Skip to content

Commit da7b072

Browse files
committed
fix: always send USERAUTH_FAILURE on reject
RFC 4252 section 5.1 has the server answer a request it does not accept with USERAUTH_FAILURE. A callback returning WOLFSSH_USERAUTH_REJECTED now always gets that reply, and always ends the session. NO_FAILURE_ON_REJECTED suppressed the reply. The macro was never set by configure, named in a header, or documented, and it guarded all four method handlers alike. - drop the macro and its four guards - end the session on a rejected keyboard-interactive setup, the one rejection that used to leave the peer retrying to the cap - state the guarantee on WOLFSSH_USERAUTH_REJECTED in wolfssh/ssh.h - add test_UserAuthRejectedSendsFailure() over the dispatched methods, asserting on the wire since the handlers return WS_USER_AUTH_E anyway Issue: F-11672
1 parent 9731cfe commit da7b072

3 files changed

Lines changed: 196 additions & 16 deletions

File tree

src/internal.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8506,11 +8506,9 @@ static int DoUserAuthRequestNone(WOLFSSH* ssh, WS_UserAuthData* authData,
85068506
}
85078507
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
85088508
WLOG(WS_LOG_DEBUG, "DUARN: none rejected");
8509-
#ifndef NO_FAILURE_ON_REJECTED
85108509
/* Count before failing: a send that blocks returns
85118510
* WS_WANT_WRITE, which isn't fatal on its own. */
85128511
(void)SendUserAuthFailureCount(ssh, 0, countIt);
8513-
#endif
85148512
ret = WS_USER_AUTH_E;
85158513
}
85168514
else if (ret == WOLFSSH_USERAUTH_WOULD_BLOCK) {
@@ -8639,9 +8637,7 @@ static int DoUserAuthInfoResponse(WOLFSSH* ssh,
86398637
}
86408638
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
86418639
WLOG(WS_LOG_DEBUG, "DUARKB: keyboard rejected");
8642-
#ifndef NO_FAILURE_ON_REJECTED
8643-
authFailure = 1;
8644-
#endif
8640+
authFailure = 1;
86458641
authRejected = 1;
86468642
ret = WS_USER_AUTH_E;
86478643
}
@@ -8754,9 +8750,7 @@ static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData,
87548750
}
87558751
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
87568752
WLOG(WS_LOG_DEBUG, "DUARPW: password rejected");
8757-
#ifndef NO_FAILURE_ON_REJECTED
8758-
authFailure = 1;
8759-
#endif
8753+
authFailure = 1;
87608754
authRejected = 1;
87618755
ret = WS_USER_AUTH_E;
87628756
}
@@ -10186,9 +10180,7 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData,
1018610180
ret = WS_AUTH_PENDING;
1018710181
}
1018810182
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
10189-
#ifndef NO_FAILURE_ON_REJECTED
10190-
authFailure = 1;
10191-
#endif
10183+
authFailure = 1;
1019210184
authRejected = 1;
1019310185
ret = WS_USER_AUTH_E;
1019410186
}
@@ -17415,6 +17407,15 @@ int SendUserAuthKeyboardRequest(WOLFSSH* ssh, WS_UserAuthData* authData)
1741517407
ssh->kbSetupPending = 0;
1741617408
return WS_AUTH_PENDING;
1741717409
}
17410+
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
17411+
/* A hard rejection ends the session here as it does in the other
17412+
* methods. Count before failing: a send that blocks returns
17413+
* WS_WANT_WRITE, which isn't fatal on its own. */
17414+
WLOG(WS_LOG_DEBUG, "SUAKR: keyboard setup rejected");
17415+
ssh->kbSetupPending = 0;
17416+
(void)SendUserAuthFailureCount(ssh, 0, 1);
17417+
return WS_USER_AUTH_E;
17418+
}
1741817419
else {
1741917420
WLOG(WS_LOG_DEBUG, "Issue with keyboard auth setup, try another "
1742017421
"auth type");

tests/unit.c

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7666,12 +7666,15 @@ static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
76667666
}
76677667
return WOLFSSH_USERAUTH_INVALID_PASSWORD;
76687668
}
7669+
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
76697670

7671+
#if defined(WOLFSSH_KEYBOARD_INTERACTIVE) || \
7672+
defined(WOLFSSH_ALLOW_USERAUTH_NONE) || !defined(WOLFSSH_NO_PUBKEY_AUTH)
76707673
/* Build a USERAUTH_REQUEST payload for auth method `method` with no method-
76717674
* specific fields, as DoUserAuthRequest() sees it with the message id already
76727675
* 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. */
7676+
* so three strings (user, service, method) suffice. The publickey path appends
7677+
* its own fields to this. Returns the payload size, 0 on overflow. */
76757678
static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
76767679
const char* method)
76777680
{
@@ -7693,7 +7696,54 @@ static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
76937696
}
76947697
return idx;
76957698
}
7696-
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
7699+
7700+
#ifndef WOLFSSH_NO_PUBKEY_AUTH
7701+
/* A public key algorithm the server offers, so the request gets past the
7702+
* algorithm match and reaches the userauth callback. */
7703+
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
7704+
#define UNIT_AUTH_PK_ALGO "ecdsa-sha2-nistp256"
7705+
#elif !defined(WOLFSSH_NO_RSA_SHA2_256)
7706+
#define UNIT_AUTH_PK_ALGO "rsa-sha2-256"
7707+
#elif !defined(WOLFSSH_NO_ED25519)
7708+
#define UNIT_AUTH_PK_ALGO "ssh-ed25519"
7709+
#endif
7710+
7711+
#ifdef UNIT_AUTH_PK_ALGO
7712+
/* Build the no-signature "publickey" USERAUTH_REQUEST, the PK_OK probe. The
7713+
* handler reads the key blob only as far as its format string before calling
7714+
* the userauth callback, so the algorithm name stands in for the key. Returns
7715+
* the payload size, 0 on overflow. */
7716+
static word32 BuildAuthPkRequest(byte* buf, word32 bufSz)
7717+
{
7718+
word32 idx, algoSz = (word32)WSTRLEN(UNIT_AUTH_PK_ALGO);
7719+
7720+
idx = BuildAuthMethodRequest(buf, bufSz, "publickey");
7721+
if (idx == 0)
7722+
return 0;
7723+
if (idx + BOOLEAN_SZ + 3 * UINT32_SZ + 2 * algoSz > bufSz)
7724+
return 0;
7725+
7726+
buf[idx++] = 0; /* has signature FALSE */
7727+
7728+
PutU32BE(buf + idx, algoSz);
7729+
idx += UINT32_SZ;
7730+
WMEMCPY(buf + idx, UNIT_AUTH_PK_ALGO, algoSz);
7731+
idx += algoSz;
7732+
7733+
/* Key blob, itself a string holding the key format. */
7734+
PutU32BE(buf + idx, UINT32_SZ + algoSz);
7735+
idx += UINT32_SZ;
7736+
PutU32BE(buf + idx, algoSz);
7737+
idx += UINT32_SZ;
7738+
WMEMCPY(buf + idx, UNIT_AUTH_PK_ALGO, algoSz);
7739+
idx += algoSz;
7740+
7741+
return idx;
7742+
}
7743+
#endif /* UNIT_AUTH_PK_ALGO */
7744+
#endif /* !WOLFSSH_NO_PUBKEY_AUTH */
7745+
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE || WOLFSSH_ALLOW_USERAUTH_NONE
7746+
* || !WOLFSSH_NO_PUBKEY_AUTH */
76977747

76987748
/* Build a "password" USERAUTH_REQUEST payload, as DoUserAuthRequest() sees it
76997749
* with the message id already consumed. Returns the payload size. */
@@ -7959,6 +8009,129 @@ static int CaptureIoSendAuthSvc(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
79598009
return (int)sz;
79608010
}
79618011

8012+
/* Userauth callback that rejects outright. */
8013+
static int UnitAuthAlwaysReject(byte authType, WS_UserAuthData* authData,
8014+
void* ctx)
8015+
{
8016+
(void)authType;
8017+
(void)authData;
8018+
(void)ctx;
8019+
return WOLFSSH_USERAUTH_REJECTED;
8020+
}
8021+
8022+
8023+
/* Drive one rejected USERAUTH_REQUEST on a fresh session and check that a
8024+
* USERAUTH_FAILURE went out. Returns 0, or 1..3 naming what failed. */
8025+
static int RunRejectedRequest(const byte* request, word32 requestSz,
8026+
const char* method)
8027+
{
8028+
WOLFSSH_CTX* ctx = NULL;
8029+
WOLFSSH* ssh = NULL;
8030+
word32 idx = 0;
8031+
int ret;
8032+
int result = 0;
8033+
8034+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
8035+
if (ctx == NULL)
8036+
return 1;
8037+
wolfSSH_SetUserAuth(ctx, UnitAuthAlwaysReject);
8038+
wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc);
8039+
8040+
ssh = wolfSSH_new(ctx);
8041+
if (ssh == NULL) {
8042+
wolfSSH_CTX_free(ctx);
8043+
return 1;
8044+
}
8045+
8046+
s_authSvcCaptureSz = 0;
8047+
s_authSvcSendCount = 0;
8048+
WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture));
8049+
8050+
ret = wolfSSH_TestDoUserAuthRequest(ssh, (byte*)request, requestSz, &idx);
8051+
8052+
if (ret != WS_USER_AUTH_E) {
8053+
printf("UserAuthRejectedSendsFailure: %s ret=%d expected %d\n",
8054+
method, ret, WS_USER_AUTH_E);
8055+
result = 1;
8056+
}
8057+
else if (s_authSvcSendCount == 0) {
8058+
printf("UserAuthRejectedSendsFailure: %s sent nothing on rejection\n",
8059+
method);
8060+
result = 2;
8061+
}
8062+
else if (CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz)
8063+
!= MSGID_USERAUTH_FAILURE) {
8064+
printf("UserAuthRejectedSendsFailure: %s msgId=%d expected"
8065+
" USERAUTH_FAILURE\n", method,
8066+
CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz));
8067+
result = 3;
8068+
}
8069+
8070+
wolfSSH_free(ssh);
8071+
wolfSSH_CTX_free(ctx);
8072+
8073+
return result;
8074+
}
8075+
8076+
8077+
/* RFC 4252 section 5.1: a rejected request is answered with
8078+
* USERAUTH_FAILURE. The handler returns WS_USER_AUTH_E either way, so the
8079+
* assertion is on what went out on the wire. Every method the build dispatches
8080+
* has its own reject branch, so each is driven here.
8081+
*
8082+
* DoUserAuthInfoResponse() is the one reject branch left uncovered; reaching
8083+
* it needs an INFO_RESPONSE shim. The keyboard case here is the setup
8084+
* callback, which answers the request before any prompt goes out. The
8085+
* publickey case needs one of the offered key algorithms compiled in. */
8086+
static int test_UserAuthRejectedSendsFailure(void)
8087+
{
8088+
byte request[128];
8089+
word32 requestSz;
8090+
int ret;
8091+
8092+
requestSz = BuildAuthPwRequest(request, (word32)sizeof(request));
8093+
if (requestSz == 0)
8094+
return -790;
8095+
ret = RunRejectedRequest(request, requestSz, "password");
8096+
if (ret != 0)
8097+
return -790 - ret;
8098+
8099+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
8100+
requestSz = BuildAuthMethodRequest(request, (word32)sizeof(request),
8101+
"keyboard-interactive");
8102+
if (requestSz == 0)
8103+
return -800;
8104+
ret = RunRejectedRequest(request, requestSz, "keyboard-interactive");
8105+
if (ret != 0)
8106+
return -800 - ret;
8107+
#endif
8108+
8109+
#ifdef WOLFSSH_ALLOW_USERAUTH_NONE
8110+
requestSz = BuildAuthMethodRequest(request, (word32)sizeof(request),
8111+
"none");
8112+
if (requestSz == 0)
8113+
return -795;
8114+
/* The opening probe is exempt from the failure count, not from the
8115+
* reply. */
8116+
ret = RunRejectedRequest(request, requestSz, "none");
8117+
if (ret != 0)
8118+
return -795 - ret;
8119+
#endif
8120+
8121+
#ifdef UNIT_AUTH_PK_ALGO
8122+
requestSz = BuildAuthPkRequest(request, (word32)sizeof(request));
8123+
if (requestSz == 0)
8124+
return -805;
8125+
/* No signature, so the callback answers the PK_OK probe. */
8126+
ret = RunRejectedRequest(request, requestSz, "publickey");
8127+
if (ret != 0)
8128+
return -805 - ret;
8129+
#endif
8130+
8131+
return 0;
8132+
}
8133+
8134+
79628135
/* Verify DoUserAuthRequest rejects non-"ssh-connection" service names per
79638136
* RFC 4252 Section 5. For each case we assert:
79648137
* 1. ret == WS_SUCCESS (connection stays open for retry)
@@ -17715,6 +17888,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
1771517888
(unitResult == 0 ? "SUCCESS" : "FAILED"));
1771617889
testResult = testResult || unitResult;
1771717890

17891+
unitResult = test_UserAuthRejectedSendsFailure();
17892+
printf("UserAuthRejectedSendsFailure: %s\n",
17893+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
17894+
testResult = testResult || unitResult;
17895+
1771817896
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && \
1771917897
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384)
1772017898
unitResult = test_EccUserAuthCurveMismatch();

wolfssh/ssh.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,9 @@ typedef struct WS_UserAuthData {
465465
* a multi-method authentication passed, WOLFSSH_USERAUTH_SUCCESS_ANOTHER
466466
* reports that a keyboard-interactive round passed and asks for the next
467467
* round, WOLFSSH_USERAUTH_WOULD_BLOCK asks for the request to be
468-
* retried, and WOLFSSH_USERAUTH_REJECTED is a hard rejection that ends
469-
* the session; any other value is treated as an ordinary failure.
468+
* retried, and WOLFSSH_USERAUTH_REJECTED is a hard rejection: the server
469+
* answers with USERAUTH_FAILURE, then ends the session. Any other value
470+
* is treated as an ordinary failure.
470471
*
471472
* WARNING: WOLFSSH_USERAUTH_SUCCESS has the value 0, the same as
472473
* WS_SUCCESS and the C "no error" idiom. A bare "return 0;", a forwarded

0 commit comments

Comments
 (0)