Skip to content

Commit a4733ec

Browse files
committed
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.
1 parent 34c3794 commit a4733ec

File tree

15 files changed

+1714
-27
lines changed

15 files changed

+1714
-27
lines changed

.github/workflows/sshd-test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ jobs:
102102
- name: Test memory after close down
103103
working-directory: ./wolfssh/
104104
run: |
105-
sudo apt-get install valgrind
105+
sudo apt-get -y update
106+
sudo apt-get -y install valgrind
106107
touch sshd_config.txt
107108
./configure --enable-all LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_FPKI -DWOLFSSH_NO_SFTP_TIMEOUT -DWOLFSSH_MAX_SFTP_RW=4000000 -DMAX_PATH_SZ=120" --enable-static --disable-shared && make
108109
sudo timeout --preserve-status -s 2 5 valgrind --error-exitcode=1 --leak-check=full ./apps/wolfsshd/wolfsshd -D -f sshd_config -h ./keys/server-key.pem -d -p 22222

examples/client/common.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ static word32 userPrivateKeySz = sizeof(userPrivateKeyBuf);
5757
static word32 userPrivateKeyTypeSz = 0;
5858
static byte isPrivate = 0;
5959

60+
static word32 keyboardResponseCount = 0;
61+
static byte** keyboardResponses;
62+
static word32* keyboardResponseLengths;
63+
6064

6165
#ifdef WOLFSSH_CERTS
6266
#if 0
@@ -445,6 +449,9 @@ int ClientUserAuth(byte authType,
445449
{
446450
const char* defaultPassword = (const char*)ctx;
447451
word32 passwordSz = 0;
452+
#ifdef WOLFSSH_TERM
453+
word32 entry;
454+
#endif
448455
int ret = WOLFSSH_USERAUTH_SUCCESS;
449456

450457
#ifdef DEBUG_WOLFSSH
@@ -456,6 +463,9 @@ int ClientUserAuth(byte authType,
456463
if (authData->type & WOLFSSH_USERAUTH_PUBLICKEY) {
457464
printf(" - publickey\n");
458465
}
466+
if (authData->type & WOLFSSH_USERAUTH_KEYBOARD) {
467+
printf(" - keyboard\n");
468+
}
459469
printf("wolfSSH requesting to use type %d\n", authType);
460470
#endif
461471

@@ -523,7 +533,63 @@ int ClientUserAuth(byte authType,
523533
authData->sf.password.passwordSz = passwordSz;
524534
}
525535
}
536+
#ifdef WOLFSSH_TERM
537+
else if (authType == WOLFSSH_USERAUTH_KEYBOARD) {
538+
if (authData->sf.keyboard.promptName &&
539+
authData->sf.keyboard.promptName[0] != '\0') {
540+
printf("%s\n", authData->sf.keyboard.promptName);
541+
}
542+
if (authData->sf.keyboard.promptInstruction &&
543+
authData->sf.keyboard.promptInstruction[0] != '\0') {
544+
printf("%s\n", authData->sf.keyboard.promptInstruction);
545+
}
546+
keyboardResponseCount = authData->sf.keyboard.promptCount;
547+
keyboardResponses =
548+
(byte**)WMALLOC(sizeof(byte*) * keyboardResponseCount, NULL, 0);
549+
if (keyboardResponses == NULL) {
550+
ret = WS_MEMORY_E;
551+
}
552+
if (ret == WS_SUCCESS) {
553+
authData->sf.keyboard.responses = (byte**)keyboardResponses;
554+
keyboardResponseLengths = (word32*)WMALLOC(
555+
sizeof(word32) * keyboardResponseCount, NULL, 0);
556+
authData->sf.keyboard.responseLengths = keyboardResponseLengths;
557+
}
526558

559+
if (keyboardResponseLengths == NULL) {
560+
ret = WS_MEMORY_E;
561+
}
562+
563+
for (entry = 0; entry < authData->sf.keyboard.promptCount; entry++) {
564+
if (ret == WS_SUCCESS) {
565+
printf("%s", authData->sf.keyboard.prompts[entry]);
566+
if (!authData->sf.keyboard.promptEcho[entry]) {
567+
ClientSetEcho(0);
568+
}
569+
if (WFGETS((char*)userPassword, sizeof(userPassword), stdin)
570+
== NULL) {
571+
fprintf(stderr, "Getting response failed.\n");
572+
ret = WOLFSSH_USERAUTH_FAILURE;
573+
}
574+
else {
575+
char* c = strpbrk((char*)userPassword, "\r\n");
576+
if (c != NULL)
577+
*c = '\0';
578+
}
579+
passwordSz = (word32)strlen((const char*)userPassword);
580+
ClientSetEcho(1);
581+
#ifdef USE_WINDOWS_API
582+
printf("\r\n");
583+
#endif
584+
WFFLUSH(stdout);
585+
authData->sf.keyboard.responses[entry] =
586+
(byte*) WSTRDUP((char*)userPassword, NULL, 0);
587+
authData->sf.keyboard.responseLengths[entry] = passwordSz;
588+
authData->sf.keyboard.responseCount++;
589+
}
590+
}
591+
}
592+
#endif
527593
return ret;
528594
}
529595

@@ -797,11 +863,18 @@ int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert)
797863
void ClientFreeBuffers(const char* pubKeyName, const char* privKeyName,
798864
void* heap)
799865
{
866+
word32 entry;
800867
if (pubKeyName != NULL && userPublicKey != NULL) {
801868
WFREE(userPublicKey, heap, DYNTYPE_PRIVKEY);
802869
}
803870

804871
if (privKeyName != NULL && userPrivateKey != NULL) {
805872
WFREE(userPrivateKey, heap, DYNTYPE_PRIVKEY);
806873
}
874+
875+
for (entry = 0; entry < keyboardResponseCount; entry++) {
876+
WFREE(keyboardResponses[entry], NULL, 0);
877+
}
878+
WFREE(keyboardResponses, NULL, 0);
879+
WFREE(keyboardResponseLengths, NULL, 0);
807880
}

examples/echoserver/echoserver.c

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,6 @@ static int callbackReqFailure(WOLFSSH *ssh, void *buf, word32 sz, void *ctx)
289289
return WS_SUCCESS;
290290
}
291291

292-
293292
static void *global_req(void *ctx)
294293
{
295294
int ret;
@@ -869,7 +868,7 @@ static int ssh_worker(thread_ctx_t* threadCtx)
869868
#if defined(HAVE_SYS_IOCTL_H)
870869
wolfSSH_DoModes(ssh->modes, ssh->modesSz, childFd);
871870
{
872-
struct winsize s = {0};
871+
struct winsize s = {0,0,0,0};
873872

874873
s.ws_col = ssh->widthChar;
875874
s.ws_row = ssh->heightRows;
@@ -1997,6 +1996,34 @@ static int LoadPasswdList(StrList* strList, PwMapList* mapList)
19971996
return count;
19981997
}
19991998

1999+
static int LoadKeyboardList(StrList* strList, PwMapList* mapList)
2000+
{
2001+
char names[256];
2002+
char* passwd;
2003+
int count = 0;
2004+
2005+
while (strList) {
2006+
WSTRNCPY(names, strList->str, sizeof names - 1);
2007+
passwd = WSTRCHR(names, ':');
2008+
if (passwd != NULL) {
2009+
*passwd = 0;
2010+
passwd++;
2011+
2012+
PwMapNew(mapList, WOLFSSH_USERAUTH_KEYBOARD,
2013+
(byte*)names, (word32)WSTRLEN(names),
2014+
(byte*)passwd, (word32)WSTRLEN(passwd));
2015+
}
2016+
else {
2017+
fprintf(stderr, "Ignoring password: %s\n", names);
2018+
}
2019+
2020+
strList = strList->next;
2021+
count++;
2022+
}
2023+
2024+
return count;
2025+
}
2026+
20002027
#ifndef NO_FILESYSTEM
20012028
static int LoadPubKeyList(StrList* strList, int format, PwMapList* mapList)
20022029
{
@@ -2103,7 +2130,8 @@ static int wsUserAuth(byte authType,
21032130
#ifdef WOLFSSH_ALLOW_USERAUTH_NONE
21042131
authType != WOLFSSH_USERAUTH_NONE &&
21052132
#endif
2106-
authType != WOLFSSH_USERAUTH_PUBLICKEY) {
2133+
authType != WOLFSSH_USERAUTH_PUBLICKEY &&
2134+
authType != WOLFSSH_USERAUTH_KEYBOARD) {
21072135

21082136
return WOLFSSH_USERAUTH_FAILURE;
21092137
}
@@ -2113,6 +2141,14 @@ static int wsUserAuth(byte authType,
21132141
authData->sf.password.passwordSz,
21142142
authHash);
21152143
}
2144+
else if (authType == WOLFSSH_USERAUTH_KEYBOARD) {
2145+
if (authData->sf.keyboard.responseCount != 1) {
2146+
return WOLFSSH_USERAUTH_FAILURE;
2147+
}
2148+
wc_Sha256Hash(authData->sf.keyboard.responses[0],
2149+
authData->sf.keyboard.responseLengths[0],
2150+
authHash);
2151+
}
21162152
else if (authType == WOLFSSH_USERAUTH_PUBLICKEY) {
21172153
wc_Sha256Hash(authData->sf.publicKey.publicKey,
21182154
authData->sf.publicKey.publicKeySz,
@@ -2213,6 +2249,14 @@ static int wsUserAuth(byte authType,
22132249
WOLFSSH_USERAUTH_REJECTED;
22142250
}
22152251
}
2252+
else if (authData->type == WOLFSSH_USERAUTH_KEYBOARD) {
2253+
if (WMEMCMP(map->p, authHash, WC_SHA256_DIGEST_SIZE) == 0) {
2254+
return WOLFSSH_USERAUTH_SUCCESS;
2255+
}
2256+
else {
2257+
return WOLFSSH_USERAUTH_INVALID_PASSWORD;
2258+
}
2259+
}
22162260
#ifdef WOLFSSH_ALLOW_USERAUTH_NONE
22172261
else if (authData->type == WOLFSSH_USERAUTH_NONE) {
22182262
return WOLFSSH_USERAUTH_SUCCESS;
@@ -2228,6 +2272,13 @@ static int wsUserAuth(byte authType,
22282272
return WOLFSSH_USERAUTH_INVALID_USER;
22292273
}
22302274

2275+
static int keyboardCallback(WS_UserAuthData_Keyboard *kbAuth, void *ctx)
2276+
{
2277+
WS_UserAuthData_Keyboard *kbAuthData = (WS_UserAuthData_Keyboard*) ctx;
2278+
WMEMCPY(kbAuth, kbAuthData, sizeof(WS_UserAuthData_Keyboard));
2279+
2280+
return WS_SUCCESS;
2281+
}
22312282

22322283
#ifdef WOLFSSH_SFTP
22332284
/*
@@ -2312,6 +2363,9 @@ static void ShowUsage(void)
23122363
" load in an X.509 DER cert to accept from peer\n");
23132364
printf(" -P <name>:<password>\n"
23142365
" add password to accept from peer\n");
2366+
printf(" -i <name>:<password>\n"
2367+
" add passowrd to accept via keyboard-interactive "
2368+
"from peer\n");
23152369
#ifdef WOLFSSH_CERTS
23162370
printf(" -a <file> load in a root CA certificate file\n");
23172371
#endif
@@ -2352,6 +2406,8 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
23522406
StrList* derPubKeyList = NULL;
23532407
#endif
23542408
StrList* passwdList = NULL;
2409+
StrList* keyboardList = NULL;
2410+
WS_UserAuthData_Keyboard kbAuthData;
23552411
WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID;
23562412
word32 defaultHighwater = EXAMPLE_HIGHWATER_MARK;
23572413
word32 threadCount = 0;
@@ -2376,9 +2432,10 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
23762432
int argc = serverArgs->argc;
23772433
char** argv = serverArgs->argv;
23782434
serverArgs->return_code = EXIT_SUCCESS;
2435+
kbAuthData.promptCount = 0;
23792436

23802437
if (argc > 0) {
2381-
const char* optlist = "?1a:d:efEp:R:Ni:j:I:J:K:P:k:b:";
2438+
const char* optlist = "?1a:d:efEp:R:Ni:j:i:I:J:K:P:k:b:";
23822439
myoptind = 0;
23832440
while ((ch = mygetopt(argc, argv, optlist)) != -1) {
23842441
switch (ch) {
@@ -2462,6 +2519,10 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
24622519
passwdList = StrListAdd(passwdList, myoptarg);
24632520
break;
24642521

2522+
case 'i':
2523+
keyboardList = StrListAdd(keyboardList, myoptarg);
2524+
break;
2525+
24652526
case 'b':
24662527
userAuthWouldBlock = atoi(myoptarg);
24672528
break;
@@ -2529,6 +2590,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
25292590
wolfSSH_SetUserAuth(ctx, wsUserAuth);
25302591
else
25312592
wolfSSH_SetUserAuth(ctx, ((func_args*)args)->user_auth);
2593+
25322594
wolfSSH_SetUserAuthResult(ctx, wsUserAuthResult);
25332595
wolfSSH_CTX_SetBanner(ctx, echoserverBanner);
25342596
#ifdef WOLFSSH_AGENT
@@ -2561,6 +2623,35 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
25612623
passwdList = NULL;
25622624
}
25632625

2626+
if (keyboardList) {
2627+
LoadKeyboardList(keyboardList, &pwMapList);
2628+
StrListFree(keyboardList);
2629+
keyboardList = NULL;
2630+
kbAuthData.promptCount = 1;
2631+
kbAuthData.promptName = NULL;
2632+
kbAuthData.promptNameSz = 0;
2633+
kbAuthData.promptInstruction = NULL;
2634+
kbAuthData.promptInstructionSz = 0;
2635+
kbAuthData.promptLanguage = NULL;
2636+
kbAuthData.promptLanguageSz = 0;
2637+
kbAuthData.prompts = (byte**)WMALLOC(sizeof(byte*), NULL, 0);
2638+
if (kbAuthData.prompts == NULL) {
2639+
ES_ERROR("Error allocating prompts");
2640+
}
2641+
kbAuthData.prompts[0] = (byte*)"KB Auth Password: ";
2642+
kbAuthData.promptLengths = (word32*)WMALLOC(sizeof(word32), NULL, 0);
2643+
if (kbAuthData.prompts == NULL) {
2644+
ES_ERROR("Error allocating promptLengths");
2645+
}
2646+
kbAuthData.promptLengths[0] = 18;
2647+
kbAuthData.promptEcho = (byte*)WMALLOC(sizeof(byte), NULL, 0);
2648+
if (kbAuthData.prompts == NULL) {
2649+
ES_ERROR("Error allocating promptEcho");
2650+
}
2651+
kbAuthData.promptEcho[0] = 0;
2652+
wolfSSH_SetKeyboardAuthPrompts(ctx, keyboardCallback);
2653+
}
2654+
25642655
{
25652656
const char* bufName = NULL;
25662657
#ifndef WOLFSSH_SMALL_STACK
@@ -2762,6 +2853,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
27622853
#endif
27632854
wolfSSH_SetUserAuthCtx(ssh, &pwMapList);
27642855
wolfSSH_SetKeyingCompletionCbCtx(ssh, (void*)ssh);
2856+
wolfSSH_SetKeyboardAuthCtx(ssh, &kbAuthData);
27652857
/* Use the session object for its own highwater callback ctx */
27662858
if (defaultHighwater > 0) {
27672859
wolfSSH_SetHighwaterCtx(ssh, (void*)ssh);
@@ -2834,6 +2926,11 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
28342926
if (listenFd != WOLFSSH_SOCKET_INVALID) {
28352927
WCLOSESOCKET(listenFd);
28362928
}
2929+
if (kbAuthData.promptCount > 0) {
2930+
WFREE(kbAuthData.promptLengths, NULL, 0);
2931+
WFREE(kbAuthData.prompts, NULL, 0);
2932+
WFREE(kbAuthData.promptEcho, NULL, 0);
2933+
}
28372934
wc_FreeMutex(&doneLock);
28382935
PwMapListDelete(&pwMapList);
28392936
wolfSSH_CTX_free(ctx);
@@ -2867,8 +2964,11 @@ int wolfSSH_Echoserver(int argc, char** argv)
28672964

28682965
WSTARTTCP();
28692966

2870-
#ifdef DEBUG_WOLFSSH
2967+
2968+
#ifdef DEBUG_WOLFSSL
28712969
wolfSSL_Debugging_ON();
2970+
#endif
2971+
#ifdef DEBUG_WOLFSSH
28722972
wolfSSH_Debugging_ON();
28732973
#endif
28742974

0 commit comments

Comments
 (0)