@@ -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. */
76757678static 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();
0 commit comments