@@ -7673,13 +7673,17 @@ static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
76737673 }
76747674 return WOLFSSH_USERAUTH_INVALID_PASSWORD;
76757675}
7676+ #endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
76767677
7678+ #if defined(WOLFSSH_KEYBOARD_INTERACTIVE) || \
7679+ defined(WOLFSSH_ALLOW_USERAUTH_NONE) || !defined(WOLFSSH_NO_PUBKEY_AUTH)
76777680/* Build a USERAUTH_REQUEST payload for `service` and auth method `method`
76787681 * with no method-specific fields, as DoUserAuthRequest() sees it with the
76797682 * 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. */
7683+ * parse trailing fields, so three strings (user, service, method) suffice,
7684+ * and the publickey path appends its own fields to this. A NULL method omits
7685+ * that field, which a bad service name never reaches. Returns the payload
7686+ * size, 0 on overflow. */
76837687static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
76847688 const char* service, const char* method)
76857689{
@@ -7703,7 +7707,54 @@ static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
77037707 }
77047708 return idx;
77057709}
7706- #endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
7710+
7711+ #ifndef WOLFSSH_NO_PUBKEY_AUTH
7712+ /* A public key algorithm the server offers, so the request gets past the
7713+ * algorithm match and reaches the userauth callback. */
7714+ #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
7715+ #define UNIT_AUTH_PK_ALGO "ecdsa-sha2-nistp256"
7716+ #elif !defined(WOLFSSH_NO_RSA_SHA2_256)
7717+ #define UNIT_AUTH_PK_ALGO "rsa-sha2-256"
7718+ #elif !defined(WOLFSSH_NO_ED25519)
7719+ #define UNIT_AUTH_PK_ALGO "ssh-ed25519"
7720+ #endif
7721+
7722+ #ifdef UNIT_AUTH_PK_ALGO
7723+ /* Build the no-signature "publickey" USERAUTH_REQUEST, the PK_OK probe. The
7724+ * handler reads the key blob only as far as its format string before calling
7725+ * the userauth callback, so the algorithm name stands in for the key. Returns
7726+ * the payload size, 0 on overflow. */
7727+ static word32 BuildAuthPkRequest(byte* buf, word32 bufSz)
7728+ {
7729+ word32 idx, algoSz = (word32)WSTRLEN(UNIT_AUTH_PK_ALGO);
7730+
7731+ idx = BuildAuthMethodRequest(buf, bufSz, "ssh-connection", "publickey");
7732+ if (idx == 0)
7733+ return 0;
7734+ if (idx + BOOLEAN_SZ + 3 * UINT32_SZ + 2 * algoSz > bufSz)
7735+ return 0;
7736+
7737+ buf[idx++] = 0; /* has signature FALSE */
7738+
7739+ PutU32BE(buf + idx, algoSz);
7740+ idx += UINT32_SZ;
7741+ WMEMCPY(buf + idx, UNIT_AUTH_PK_ALGO, algoSz);
7742+ idx += algoSz;
7743+
7744+ /* Key blob, itself a string holding the key format. */
7745+ PutU32BE(buf + idx, UINT32_SZ + algoSz);
7746+ idx += UINT32_SZ;
7747+ PutU32BE(buf + idx, algoSz);
7748+ idx += UINT32_SZ;
7749+ WMEMCPY(buf + idx, UNIT_AUTH_PK_ALGO, algoSz);
7750+ idx += algoSz;
7751+
7752+ return idx;
7753+ }
7754+ #endif /* UNIT_AUTH_PK_ALGO */
7755+ #endif /* !WOLFSSH_NO_PUBKEY_AUTH */
7756+ #endif /* WOLFSSH_KEYBOARD_INTERACTIVE || WOLFSSH_ALLOW_USERAUTH_NONE
7757+ * || !WOLFSSH_NO_PUBKEY_AUTH */
77077758
77087759/* Build a "password" USERAUTH_REQUEST payload, as DoUserAuthRequest() sees it
77097760 * with the message id already consumed. Returns the payload size. */
@@ -7969,6 +8020,226 @@ static int CaptureIoSendAuthSvc(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
79698020 return (int)sz;
79708021}
79718022
8023+ /* Userauth callback that rejects outright. */
8024+ static int UnitAuthAlwaysReject(byte authType, WS_UserAuthData* authData,
8025+ void* ctx)
8026+ {
8027+ (void)authType;
8028+ (void)authData;
8029+ (void)ctx;
8030+ return WOLFSSH_USERAUTH_REJECTED;
8031+ }
8032+
8033+
8034+ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE
8035+ /* Accept the setup so the exchange gets past SendUserAuthKeyboardRequest(),
8036+ * then reject the response, which is the DoUserAuthInfoResponse() branch. */
8037+ static int UnitAuthKbRespReject(byte authType, WS_UserAuthData* authData,
8038+ void* ctx)
8039+ {
8040+ (void)ctx;
8041+ if (authType == WOLFSSH_USERAUTH_KEYBOARD_SETUP) {
8042+ authData->sf.keyboard.promptCount = 0;
8043+ return WOLFSSH_USERAUTH_SUCCESS;
8044+ }
8045+ return WOLFSSH_USERAUTH_REJECTED;
8046+ }
8047+
8048+
8049+ /* Drive an accepted keyboard-interactive setup, then a rejected
8050+ * INFO_RESPONSE, and check the rejection drew a USERAUTH_FAILURE. Returns 0,
8051+ * or 1..5 naming what failed. */
8052+ static int RunRejectedInfoResponse(void)
8053+ {
8054+ WOLFSSH_CTX* ctx = NULL;
8055+ WOLFSSH* ssh = NULL;
8056+ byte request[64];
8057+ byte infoRsp[UINT32_SZ];
8058+ word32 requestSz, idx = 0;
8059+ int ret;
8060+ int result = 0;
8061+
8062+ requestSz = BuildAuthMethodRequest(request, (word32)sizeof(request),
8063+ "ssh-connection", "keyboard-interactive");
8064+ if (requestSz == 0)
8065+ return 1;
8066+
8067+ /* INFO_RESPONSE body: response count of 0, matching promptCount. */
8068+ PutU32BE(infoRsp, 0);
8069+
8070+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
8071+ if (ctx == NULL)
8072+ return 1;
8073+ wolfSSH_SetUserAuth(ctx, UnitAuthKbRespReject);
8074+ wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc);
8075+
8076+ ssh = wolfSSH_new(ctx);
8077+ if (ssh == NULL) {
8078+ wolfSSH_CTX_free(ctx);
8079+ return 1;
8080+ }
8081+
8082+ ret = wolfSSH_TestDoUserAuthRequest(ssh, request, requestSz, &idx);
8083+ if (ret != WS_SUCCESS) {
8084+ printf("UserAuthRejectedSendsFailure: kb setup ret=%d expected %d\n",
8085+ ret, WS_SUCCESS);
8086+ result = 2;
8087+ }
8088+
8089+ if (result == 0) {
8090+ /* Drop the INFO_REQUEST the setup sent; the assertion is on what the
8091+ * response draws. */
8092+ s_authSvcCaptureSz = 0;
8093+ s_authSvcSendCount = 0;
8094+ WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture));
8095+
8096+ idx = 0;
8097+ ret = wolfSSH_TestDoUserAuthInfoResponse(ssh, infoRsp,
8098+ (word32)sizeof(infoRsp), &idx);
8099+
8100+ if (ret != WS_USER_AUTH_E) {
8101+ printf("UserAuthRejectedSendsFailure: info response ret=%d "
8102+ "expected %d\n", ret, WS_USER_AUTH_E);
8103+ result = 3;
8104+ }
8105+ else if (s_authSvcSendCount == 0) {
8106+ printf("UserAuthRejectedSendsFailure: info response sent nothing "
8107+ "on rejection\n");
8108+ result = 4;
8109+ }
8110+ else if (CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz)
8111+ != MSGID_USERAUTH_FAILURE) {
8112+ printf("UserAuthRejectedSendsFailure: info response msgId=%d "
8113+ "expected USERAUTH_FAILURE\n",
8114+ CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz));
8115+ result = 5;
8116+ }
8117+ }
8118+
8119+ wolfSSH_free(ssh);
8120+ wolfSSH_CTX_free(ctx);
8121+
8122+ return result;
8123+ }
8124+ #endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
8125+
8126+
8127+ /* Drive one rejected USERAUTH_REQUEST on a fresh session and check that a
8128+ * USERAUTH_FAILURE went out. Returns 0, or 1..3 naming what failed. */
8129+ static int RunRejectedRequest(const byte* request, word32 requestSz,
8130+ const char* method)
8131+ {
8132+ WOLFSSH_CTX* ctx = NULL;
8133+ WOLFSSH* ssh = NULL;
8134+ word32 idx = 0;
8135+ int ret;
8136+ int result = 0;
8137+
8138+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
8139+ if (ctx == NULL)
8140+ return 1;
8141+ wolfSSH_SetUserAuth(ctx, UnitAuthAlwaysReject);
8142+ wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc);
8143+
8144+ ssh = wolfSSH_new(ctx);
8145+ if (ssh == NULL) {
8146+ wolfSSH_CTX_free(ctx);
8147+ return 1;
8148+ }
8149+
8150+ s_authSvcCaptureSz = 0;
8151+ s_authSvcSendCount = 0;
8152+ WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture));
8153+
8154+ ret = wolfSSH_TestDoUserAuthRequest(ssh, (byte*)request, requestSz, &idx);
8155+
8156+ if (ret != WS_USER_AUTH_E) {
8157+ printf("UserAuthRejectedSendsFailure: %s ret=%d expected %d\n",
8158+ method, ret, WS_USER_AUTH_E);
8159+ result = 1;
8160+ }
8161+ else if (s_authSvcSendCount == 0) {
8162+ printf("UserAuthRejectedSendsFailure: %s sent nothing on rejection\n",
8163+ method);
8164+ result = 2;
8165+ }
8166+ else if (CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz)
8167+ != MSGID_USERAUTH_FAILURE) {
8168+ printf("UserAuthRejectedSendsFailure: %s msgId=%d expected"
8169+ " USERAUTH_FAILURE\n", method,
8170+ CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz));
8171+ result = 3;
8172+ }
8173+
8174+ wolfSSH_free(ssh);
8175+ wolfSSH_CTX_free(ctx);
8176+
8177+ return result;
8178+ }
8179+
8180+
8181+ /* RFC 4252 section 5.1: a rejected request is answered with
8182+ * USERAUTH_FAILURE. The handler returns WS_USER_AUTH_E either way, so the
8183+ * assertion is on what went out on the wire. Every method the build dispatches
8184+ * has its own reject branch, so each is driven here.
8185+ *
8186+ * The keyboard method has two: the setup callback, which answers the request
8187+ * before any prompt goes out, and the response callback, driven separately
8188+ * through the INFO_RESPONSE shim. The publickey case needs one of the offered
8189+ * key algorithms compiled in. */
8190+ static int test_UserAuthRejectedSendsFailure(void)
8191+ {
8192+ byte request[128];
8193+ word32 requestSz;
8194+ int ret;
8195+
8196+ requestSz = BuildAuthPwRequest(request, (word32)sizeof(request));
8197+ if (requestSz == 0)
8198+ return -790;
8199+ ret = RunRejectedRequest(request, requestSz, "password");
8200+ if (ret != 0)
8201+ return -790 - ret;
8202+
8203+ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE
8204+ requestSz = BuildAuthMethodRequest(request, (word32)sizeof(request),
8205+ "ssh-connection", "keyboard-interactive");
8206+ if (requestSz == 0)
8207+ return -800;
8208+ ret = RunRejectedRequest(request, requestSz, "keyboard-interactive");
8209+ if (ret != 0)
8210+ return -800 - ret;
8211+
8212+ ret = RunRejectedInfoResponse();
8213+ if (ret != 0)
8214+ return -810 - ret;
8215+ #endif
8216+
8217+ #ifdef WOLFSSH_ALLOW_USERAUTH_NONE
8218+ requestSz = BuildAuthMethodRequest(request, (word32)sizeof(request),
8219+ "ssh-connection", "none");
8220+ if (requestSz == 0)
8221+ return -795;
8222+ /* The opening probe is exempt from the failure count, not from the
8223+ * reply. */
8224+ ret = RunRejectedRequest(request, requestSz, "none");
8225+ if (ret != 0)
8226+ return -795 - ret;
8227+ #endif
8228+
8229+ #ifdef UNIT_AUTH_PK_ALGO
8230+ requestSz = BuildAuthPkRequest(request, (word32)sizeof(request));
8231+ if (requestSz == 0)
8232+ return -805;
8233+ /* No signature, so the callback answers the PK_OK probe. */
8234+ ret = RunRejectedRequest(request, requestSz, "publickey");
8235+ if (ret != 0)
8236+ return -805 - ret;
8237+ #endif
8238+
8239+ return 0;
8240+ }
8241+
8242+
79728243/* Verify DoUserAuthRequest rejects non-"ssh-connection" service names per
79738244 * RFC 4252 Section 5. For each case we assert:
79748245 * 1. ret == WS_SUCCESS (connection stays open for retry)
@@ -17840,6 +18111,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
1784018111 (unitResult == 0 ? "SUCCESS" : "FAILED"));
1784118112 testResult = testResult || unitResult;
1784218113
18114+ unitResult = test_UserAuthRejectedSendsFailure();
18115+ printf("UserAuthRejectedSendsFailure: %s\n",
18116+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
18117+ testResult = testResult || unitResult;
18118+
1784318119#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && \
1784418120 !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384)
1784518121 unitResult = test_EccUserAuthCurveMismatch();
0 commit comments