Skip to content

Commit 83cef4e

Browse files
committed
Reject password-change auth requests
- fail userauth when the request sets the password-change flag - do not invoke the userauth callback with the current password - parse the new-password field so the message is fully consumed - add negative unit test asserting USERAUTH_FAILURE and no callback Per RFC 4252 section 8, an expired password MUST NOT be used to authenticate; password changes remain unsupported. Issue: #1047 (6)
1 parent 27353a4 commit 83cef4e

2 files changed

Lines changed: 146 additions & 32 deletions

File tree

src/internal.c

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7426,51 +7426,59 @@ static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData,
74267426

74277427
if (ret == WS_SUCCESS) {
74287428
if (pw->hasNewPassword) {
7429-
/* Skip the password change. Maybe error out since we aren't
7430-
* supporting password changes at this time. */
7429+
/* Password changes are not supported. Parse the new password
7430+
* field so the message is fully consumed, then reject the
7431+
* request rather than authenticating with the current password
7432+
* (RFC 4252 section 8: an expired password MUST NOT be used for
7433+
* authentication). The userauth callback is not called. */
74317434
ret = GetStringRef(&pw->newPasswordSz, &pw->newPassword,
74327435
buf, len, &begin);
7436+
if (ret == WS_SUCCESS) {
7437+
WLOG(WS_LOG_DEBUG,
7438+
"DUARPW: rejecting unsupported password change request");
7439+
authFailure = 1;
7440+
}
74337441
}
74347442
else {
74357443
pw->newPassword = NULL;
74367444
pw->newPasswordSz = 0;
7437-
}
74387445

7439-
if (ssh->ctx->userAuthCb != NULL) {
7440-
WLOG(WS_LOG_DEBUG, "DUARPW: Calling the userauth callback");
7441-
ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PASSWORD,
7442-
authData, ssh->userAuthCtx);
7443-
if (ret == WOLFSSH_USERAUTH_SUCCESS) {
7444-
WLOG(WS_LOG_DEBUG, "DUARPW: password check success");
7445-
ret = WS_SUCCESS;
7446-
}
7447-
else if (ret == WOLFSSH_USERAUTH_PARTIAL_SUCCESS) {
7448-
WLOG(WS_LOG_DEBUG, "DUARPW: password check partial success");
7449-
partialSuccess = 1;
7450-
ret = WS_SUCCESS;
7451-
}
7452-
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
7453-
WLOG(WS_LOG_DEBUG, "DUARPW: password rejected");
7454-
#ifndef NO_FAILURE_ON_REJECTED
7446+
if (ssh->ctx->userAuthCb != NULL) {
7447+
WLOG(WS_LOG_DEBUG, "DUARPW: Calling the userauth callback");
7448+
ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PASSWORD,
7449+
authData, ssh->userAuthCtx);
7450+
if (ret == WOLFSSH_USERAUTH_SUCCESS) {
7451+
WLOG(WS_LOG_DEBUG, "DUARPW: password check success");
7452+
ret = WS_SUCCESS;
7453+
}
7454+
else if (ret == WOLFSSH_USERAUTH_PARTIAL_SUCCESS) {
7455+
WLOG(WS_LOG_DEBUG, "DUARPW: password check partial success");
7456+
partialSuccess = 1;
7457+
ret = WS_SUCCESS;
7458+
}
7459+
else if (ret == WOLFSSH_USERAUTH_REJECTED) {
7460+
WLOG(WS_LOG_DEBUG, "DUARPW: password rejected");
7461+
#ifndef NO_FAILURE_ON_REJECTED
7462+
authFailure = 1;
7463+
#endif
7464+
authRejected = 1;
7465+
ret = WS_USER_AUTH_E;
7466+
}
7467+
else if (ret == WOLFSSH_USERAUTH_WOULD_BLOCK) {
7468+
WLOG(WS_LOG_DEBUG, "DUARPW: userauth callback would block");
7469+
ret = WS_AUTH_PENDING;
7470+
}
7471+
else {
7472+
WLOG(WS_LOG_DEBUG, "DUARPW: password check failed, retry");
74557473
authFailure = 1;
7456-
#endif
7457-
authRejected = 1;
7458-
ret = WS_USER_AUTH_E;
7459-
}
7460-
else if (ret == WOLFSSH_USERAUTH_WOULD_BLOCK) {
7461-
WLOG(WS_LOG_DEBUG, "DUARPW: userauth callback would block");
7462-
ret = WS_AUTH_PENDING;
7474+
ret = WS_SUCCESS;
7475+
}
74637476
}
74647477
else {
7465-
WLOG(WS_LOG_DEBUG, "DUARPW: password check failed, retry");
7478+
WLOG(WS_LOG_DEBUG, "DUARPW: No user auth callback");
74667479
authFailure = 1;
7467-
ret = WS_SUCCESS;
74687480
}
74697481
}
7470-
else {
7471-
WLOG(WS_LOG_DEBUG, "DUARPW: No user auth callback");
7472-
authFailure = 1;
7473-
}
74747482
}
74757483

74767484
if (ret == WS_SUCCESS)

tests/unit.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,107 @@ static int test_DoUserAuthRequest_serviceName(void)
23092309
}
23102310

23112311

2312+
/* userauth callback that records whether it was invoked. Returns SUCCESS so
2313+
* that, if it were ever reached for a password-change request, the request
2314+
* would be (incorrectly) authenticated - making a missed rejection visible. */
2315+
static int s_pwChangeCbCalled = 0;
2316+
static int UnitAuthAlwaysSucceed(byte authType, WS_UserAuthData* authData,
2317+
void* ctx)
2318+
{
2319+
(void)authData;
2320+
(void)ctx;
2321+
if (authType == WOLFSSH_USERAUTH_PASSWORD) {
2322+
s_pwChangeCbCalled = 1;
2323+
}
2324+
return WOLFSSH_USERAUTH_SUCCESS;
2325+
}
2326+
2327+
/* Verify DoUserAuthRequest rejects a password request that sets the
2328+
* password-change flag (RFC 4252 Section 8: an expired password MUST NOT be
2329+
* used for authentication). The request is otherwise well-formed and the
2330+
* userauth callback would return SUCCESS, so a missing rejection would let the
2331+
* old password authenticate. Asserts:
2332+
* 1. ret == WS_SUCCESS (connection stays open for retry)
2333+
* 2. the userauth callback is never invoked
2334+
* 3. exactly one packet is sent and it is SSH_MSG_USERAUTH_FAILURE
2335+
* 4. *idx == len (the new-password field is fully consumed) */
2336+
static int test_DoUserAuthRequest_rejectsPasswordChange(void)
2337+
{
2338+
WOLFSSH_CTX* ctx = NULL;
2339+
WOLFSSH* ssh = NULL;
2340+
int result = 0;
2341+
int ret;
2342+
int capMsgId;
2343+
byte buf[128];
2344+
word32 len = 0, idx = 0;
2345+
2346+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2347+
if (ctx == NULL)
2348+
return -660;
2349+
wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc);
2350+
wolfSSH_SetUserAuth(ctx, UnitAuthAlwaysSucceed);
2351+
2352+
ssh = wolfSSH_new(ctx);
2353+
if (ssh == NULL) {
2354+
result = -661;
2355+
goto out;
2356+
}
2357+
2358+
s_pwChangeCbCalled = 0;
2359+
s_authSvcCaptureSz = 0;
2360+
s_authSvcSendCount = 0;
2361+
WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture));
2362+
2363+
/* username: "user" */
2364+
buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 4;
2365+
WMEMCPY(buf + len, "user", 4); len += 4;
2366+
/* service name: "ssh-connection" */
2367+
buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 14;
2368+
WMEMCPY(buf + len, "ssh-connection", 14); len += 14;
2369+
/* auth method: "password" */
2370+
buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 8;
2371+
WMEMCPY(buf + len, "password", 8); len += 8;
2372+
/* password-change flag: TRUE */
2373+
buf[len++] = 1;
2374+
/* current password: "oldpass" */
2375+
buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 7;
2376+
WMEMCPY(buf + len, "oldpass", 7); len += 7;
2377+
/* new password: "newpass" */
2378+
buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 7;
2379+
WMEMCPY(buf + len, "newpass", 7); len += 7;
2380+
2381+
ret = wolfSSH_TestDoUserAuthRequest(ssh, buf, len, &idx);
2382+
2383+
if (ret != WS_SUCCESS) {
2384+
result = -662;
2385+
goto out;
2386+
}
2387+
if (s_pwChangeCbCalled) {
2388+
/* The callback must not run for a password-change request. */
2389+
result = -663;
2390+
goto out;
2391+
}
2392+
if (s_authSvcSendCount != 1) {
2393+
result = -664;
2394+
goto out;
2395+
}
2396+
capMsgId = CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz);
2397+
if (capMsgId != MSGID_USERAUTH_FAILURE) {
2398+
result = -665;
2399+
goto out;
2400+
}
2401+
if (idx != len) {
2402+
result = -666;
2403+
goto out;
2404+
}
2405+
2406+
out:
2407+
wolfSSH_free(ssh);
2408+
wolfSSH_CTX_free(ctx);
2409+
return result;
2410+
}
2411+
2412+
23122413
/* userAuthTypesCb that advertises no methods (returns mask 0). Mirrors a
23132414
* wolfsshd configuration with both PasswordAuthentication no and
23142415
* PubkeyAuthentication no. */
@@ -5490,6 +5591,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
54905591
(unitResult == 0 ? "SUCCESS" : "FAILED"));
54915592
testResult = testResult || unitResult;
54925593

5594+
unitResult = test_DoUserAuthRequest_rejectsPasswordChange();
5595+
printf("DoUserAuthRequest_rejectsPasswordChange: %s\n",
5596+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
5597+
testResult = testResult || unitResult;
5598+
54935599
unitResult = test_SendUserAuthFailure_emptyMethods();
54945600
printf("SendUserAuthFailure_emptyMethods: %s\n",
54955601
(unitResult == 0 ? "SUCCESS" : "FAILED"));

0 commit comments

Comments
 (0)