@@ -7654,6 +7654,10 @@ static int UnitAuthAlwaysFail(byte authType, WS_UserAuthData* authData,
76547654}
76557655
76567656#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
7657+ /* Set when the callback sees a response, so a processed INFO_RESPONSE is
7658+ * visible even when rejected. */
7659+ static int s_kbRespCbCalled = 0;
7660+
76577661/* Keyboard setup that accepts, sending an INFO_REQUEST with no prompts so the
76587662 * exchange goes outstanding (kbSetupPending). Every other outcome fails. */
76597663static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
@@ -7664,25 +7668,31 @@ static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
76647668 authData->sf.keyboard.promptCount = 0;
76657669 return WOLFSSH_USERAUTH_SUCCESS;
76667670 }
7671+ if (authType == WOLFSSH_USERAUTH_KEYBOARD) {
7672+ s_kbRespCbCalled = 1;
7673+ }
76677674 return WOLFSSH_USERAUTH_INVALID_PASSWORD;
76687675}
76697676
7670- /* Build a USERAUTH_REQUEST payload for auth method `method` with no method-
7671- * specific fields, as DoUserAuthRequest() sees it with the message id already
7672- * consumed. The keyboard and none dispatch paths don't parse trailing fields,
7673- * so three strings (user, service, method) suffice. Returns the payload size,
7674- * 0 on overflow. */
7677+ /* Build a USERAUTH_REQUEST payload for `service` and auth method `method`
7678+ * with no method-specific fields, as DoUserAuthRequest() sees it with the
7679+ * message id already consumed. The keyboard and none dispatch paths don't
7680+ * parse trailing fields, so three strings (user, service, method) suffice. A
7681+ * NULL method omits that field, which a bad service name never reaches.
7682+ * Returns the payload size, 0 on overflow. */
76757683static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
7676- const char* method)
7684+ const char* service, const char* method)
76777685{
76787686 const char* fields[3];
7679- word32 idx = 0, i, fieldSz;
7687+ word32 idx = 0, i, fieldCount;
7688+ word32 fieldSz;
76807689
76817690 fields[0] = "jill";
7682- fields[1] = "ssh-connection" ;
7691+ fields[1] = service ;
76837692 fields[2] = method;
7693+ fieldCount = (method == NULL) ? 2 : 3;
76847694
7685- for (i = 0; i < 3 ; i++) {
7695+ for (i = 0; i < fieldCount ; i++) {
76867696 fieldSz = (word32)WSTRLEN(fields[i]);
76877697 if (idx + UINT32_SZ + fieldSz > bufSz)
76887698 return 0;
@@ -7857,7 +7867,7 @@ static int test_MaxAuthAttempts(void)
78577867 WOLFSSH* ssh = NULL;
78587868 byte kbReq[64];
78597869 word32 kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
7860- "keyboard-interactive");
7870+ "ssh-connection", " keyboard-interactive");
78617871
78627872 if (kbReqSz == 0)
78637873 result = -750;
@@ -7906,7 +7916,7 @@ static int test_MaxAuthAttempts(void)
79067916 WOLFSSH* ssh = NULL;
79077917 byte kbReq[64];
79087918 word32 kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
7909- "keyboard-interactive");
7919+ "ssh-connection", " keyboard-interactive");
79107920 int attempt;
79117921
79127922 if (kbReqSz == 0)
@@ -8124,6 +8134,114 @@ static int test_DoUserAuthRequest_serviceName(void)
81248134}
81258135
81268136
8137+ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE
8138+ /* Counts KEYBOARD (response) callbacks, so a processed INFO_RESPONSE is
8139+ * visible even when rejected. */
8140+ /* A USERAUTH_REQUEST naming a service other than "ssh-connection" must not
8141+ * leave a keyboard-interactive exchange outstanding.
8142+ *
8143+ * Asserts, in order: an accepted keyboard request leaves the exchange
8144+ * outstanding; a bad-service request clears authId and kbSetupPending; a
8145+ * following INFO_RESPONSE is rejected without reaching the callback. */
8146+ static int test_DoUserAuthRequest_badServiceClearsKb(void)
8147+ {
8148+ WOLFSSH_CTX* ctx = NULL;
8149+ WOLFSSH* ssh = NULL;
8150+ byte kbReq[64], badReq[64], infoRsp[UINT32_SZ];
8151+ word32 kbReqSz, badReqSz, idx;
8152+ int result = 0;
8153+ int ret;
8154+
8155+ kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
8156+ "ssh-connection", "keyboard-interactive");
8157+ badReqSz = BuildAuthMethodRequest(badReq, (word32)sizeof(badReq),
8158+ "ssh-userauth", NULL);
8159+ if (kbReqSz == 0 || badReqSz == 0)
8160+ return -800;
8161+
8162+ /* INFO_RESPONSE body: response count of 0, matching promptCount. */
8163+ PutU32BE(infoRsp, 0);
8164+
8165+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
8166+ if (ctx == NULL)
8167+ return -801;
8168+ wolfSSH_SetUserAuth(ctx, UnitAuthKbSetupOk);
8169+ wolfSSH_SetIOSend(ctx, UnitIoSendSink);
8170+
8171+ ssh = wolfSSH_new(ctx);
8172+ if (ssh == NULL) {
8173+ wolfSSH_CTX_free(ctx);
8174+ return -802;
8175+ }
8176+ s_kbRespCbCalled = 0;
8177+
8178+ idx = 0;
8179+ ret = wolfSSH_TestDoUserAuthRequest(ssh, kbReq, kbReqSz, &idx);
8180+ if (ret != WS_SUCCESS) {
8181+ printf("badServiceClearsKb: kb request ret=%d expected %d\n",
8182+ ret, WS_SUCCESS);
8183+ result = -803;
8184+ }
8185+ if (result == 0 && (ssh->authId != ID_USERAUTH_KEYBOARD
8186+ || !ssh->kbSetupPending)) {
8187+ printf("badServiceClearsKb: kb exchange not outstanding "
8188+ "(authId=%d pending=%d)\n", ssh->authId, ssh->kbSetupPending);
8189+ result = -804;
8190+ }
8191+
8192+ if (result == 0) {
8193+ idx = 0;
8194+ ret = wolfSSH_TestDoUserAuthRequest(ssh, badReq, badReqSz, &idx);
8195+ if (ret != WS_SUCCESS) {
8196+ printf("badServiceClearsKb: bad service ret=%d expected %d\n",
8197+ ret, WS_SUCCESS);
8198+ result = -805;
8199+ }
8200+ }
8201+
8202+ if (result == 0 && ssh->authId == ID_USERAUTH_KEYBOARD) {
8203+ printf("badServiceClearsKb: authId still ID_USERAUTH_KEYBOARD after "
8204+ "bad service name\n");
8205+ result = -806;
8206+ }
8207+
8208+ if (result == 0 && ssh->kbSetupPending) {
8209+ printf("badServiceClearsKb: kbSetupPending still set after bad "
8210+ "service name\n");
8211+ result = -807;
8212+ }
8213+
8214+ /* One charge for the refusal, one for the exchange it abandoned. */
8215+ if (result == 0 && ssh->authFailures != 2) {
8216+ printf("badServiceClearsKb: authFailures=%u expected 2\n",
8217+ ssh->authFailures);
8218+ result = -810;
8219+ }
8220+
8221+ if (result == 0) {
8222+ idx = 0;
8223+ ret = wolfSSH_TestDoUserAuthInfoResponse(ssh, infoRsp,
8224+ (word32)sizeof(infoRsp), &idx);
8225+ if (ret != WS_FATAL_ERROR) {
8226+ printf("badServiceClearsKb: stale INFO_RESPONSE ret=%d "
8227+ "expected %d\n", ret, WS_FATAL_ERROR);
8228+ result = -808;
8229+ }
8230+ }
8231+
8232+ if (result == 0 && s_kbRespCbCalled) {
8233+ printf("badServiceClearsKb: userauth callback reached by stale "
8234+ "INFO_RESPONSE\n");
8235+ result = -809;
8236+ }
8237+
8238+ wolfSSH_free(ssh);
8239+ wolfSSH_CTX_free(ctx);
8240+ return result;
8241+ }
8242+ #endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
8243+
8244+
81278245/* userauth callback that records whether it was invoked. Returns SUCCESS so
81288246 * that, if it were ever reached for a password-change request, the request
81298247 * would be (incorrectly) authenticated - making a missed rejection visible. */
@@ -17705,6 +17823,13 @@ int wolfSSH_UnitTest(int argc, char** argv)
1770517823 (unitResult == 0 ? "SUCCESS" : "FAILED"));
1770617824 testResult = testResult || unitResult;
1770717825
17826+ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE
17827+ unitResult = test_DoUserAuthRequest_badServiceClearsKb();
17828+ printf("DoUserAuthRequest_badServiceClearsKb: %s\n",
17829+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
17830+ testResult = testResult || unitResult;
17831+ #endif
17832+
1770817833 unitResult = test_DoUserAuthRequest_rejectsPasswordChange();
1770917834 printf("DoUserAuthRequest_rejectsPasswordChange: %s\n",
1771017835 (unitResult == 0 ? "SUCCESS" : "FAILED"));
0 commit comments