Skip to content

Commit

Permalink
RFC-4256 Keyboard-Interactive authentication
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
LinuxJedi committed Jan 31, 2025
1 parent 86499a5 commit 9e7bd3f
Show file tree
Hide file tree
Showing 13 changed files with 1,591 additions and 22 deletions.
64 changes: 63 additions & 1 deletion examples/client/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -797,11 +852,18 @@ 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);
}

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);
}
94 changes: 91 additions & 3 deletions examples/echoserver/echoserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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
/*
Expand Down Expand Up @@ -2312,6 +2363,9 @@ static void ShowUsage(void)
" load in an X.509 DER cert to accept from peer\n");
printf(" -P <name>:<password>\n"
" add password to accept from peer\n");
printf(" -i <name>:<password>\n"
" add passowrd to accept via keyboard-interactive "
"from peer\n");
#ifdef WOLFSSH_CERTS
printf(" -a <file> load in a root CA certificate file\n");
#endif
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 9e7bd3f

Please sign in to comment.