diff --git a/src/internal.c b/src/internal.c index b0dba8d6b..e2ae79b14 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6956,11 +6956,13 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) enum wc_HashType hashId = (enum wc_HashType)ssh->handshake->kexHashId; byte scratchLen[LENGTH_SZ]; word32 strSz = 0; + int sendRet = WS_SUCCESS; /* respond with KEX Init message if not having initiated the keying */ if ((ssh->isKeying & WOLFSSH_SELF_IS_KEYING) == 0) { WLOG(WS_LOG_DEBUG, "Keying initiated"); - ret = SendKexInit(ssh); + sendRet = SendKexInit(ssh); + ret = sendRet; } /* account for possible want write case from SendKexInit */ @@ -7029,9 +7031,10 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) else ssh->serverState = SERVER_KEXINIT_DONE; - /* Propagate potential want write case from SendKexInit. */ - if (ssh->error != 0) - ret = ssh->error; + /* SendKexInit() queued its reply but could not flush it; report + * that to the caller. Its other failures return before here. */ + if (sendRet == WS_WANT_WRITE) + ret = WS_WANT_WRITE; } } /* RFC 4253 7.1: no common algorithm means both sides disconnect. */ diff --git a/tests/regress.c b/tests/regress.c index e5b0b7859..ab626df2c 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -693,11 +693,12 @@ static word32 LoadFileBuffer(const char* path, byte* buf, word32 bufSz) * UNIMPLEMENTED, having already counted the packet. */ #define REGRESS_UNASSIGNED_TRANS_MSGID 9 +#define REGRESS_SERVER_KEY_PATH "keys/server-key-rsa.der" + #ifdef KEXDH_REPLY_REGRESS_KEX_ALGO #define REGRESS_DUPLEX_QUEUE_SZ 32768U #define REGRESS_MUTATION_SCRATCH_SZ 4096U -#define REGRESS_SERVER_KEY_PATH "keys/server-key-rsa.der" #define REGRESS_SERVER_KEY_ECC_PATH "keys/server-key-ecc.der" #define REGRESS_SERVER_KEY_ED25519_PATH "keys/server-key-ed25519.der" #define REGRESS_USERNAME "jill" @@ -15787,6 +15788,116 @@ static void TestKexInitReservedNonZeroRejected(void) wolfSSH_CTX_free(ctx); } +#ifndef NO_WOLFSSH_SERVER +/* What becomes of the server's reply to the peer's KEXINIT. */ +enum { + KEXINIT_REPLY_SENT, /* goes out */ + KEXINIT_REPLY_BLOCKED, /* send would block */ + KEXINIT_REPLY_FAILED, /* send fails outright */ + KEXINIT_REPLY_NONE /* not owed: the server sent its own first */ +}; + +/* Run the peer's KEXINIT through DoKexInit() on a server, with ssh->error + * holding staleError and the reply handled as reply says. */ +static int RunKexInitWithError(int staleError, int reply) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + MemIo io; + byte keyBuf[2048]; + byte payload[512]; + byte out[2048]; + word32 keySz; + word32 payloadSz; + word32 idx = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(ctx); + wolfSSH_SetIORecv(ctx, MemRecv); + wolfSSH_SetIOSend(ctx, MemSend); + keySz = LoadFileBuffer(REGRESS_SERVER_KEY_PATH, keyBuf, sizeof(keyBuf)); + AssertTrue(keySz > 0); + AssertIntEQ(wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyBuf, keySz, + WOLFSSH_FORMAT_ASN1), WS_SUCCESS); + + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + MemIoInit(&io, NULL, 0, out, sizeof(out)); + wolfSSH_SetIOReadCtx(ssh, &io); + wolfSSH_SetIOWriteCtx(ssh, &io); + AssertIntEQ(wolfSSH_SetHighwater(ssh, 0), WS_SUCCESS); + AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS); + AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS); + + if (reply == KEXINIT_REPLY_NONE) { + AssertIntEQ(SendKexInit(ssh), WS_SUCCESS); + io.outSz = 0; + } + else if (reply == KEXINIT_REPLY_BLOCKED) { + io.blockNext = 1; + } + else if (reply == KEXINIT_REPLY_FAILED) { + io.outCap = 0; + } + + payloadSz = BuildKexInitPayload(ssh, FPF_KEX_GOOD, FPF_KEY_GOOD, + 0, payload, (word32)sizeof(payload)); + + ssh->error = staleError; + ret = wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx); + + if (reply == KEXINIT_REPLY_SENT) { + AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_KEXINIT); + } + else { + AssertIntEQ(io.outSz, 0); + } + + /* Short of a failed send, the KEXINIT is consumed and the handshake + * moves on, a blocked reply included. */ + if (reply != KEXINIT_REPLY_FAILED) { + AssertIntEQ(idx, payloadSz); + AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE); + } + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + + return ret; +} + +/* DoKexInit() reported any nonzero ssh->error once it had handled the + * KEXINIT, to pass on a blocked reply. A WS_WINDOW_FULL an earlier + * SendChannelData() left there failed the peer's rekey. */ +static void TestKexInitIgnoresStaleError(void) +{ + AssertIntEQ(RunKexInitWithError(WS_SUCCESS, KEXINIT_REPLY_SENT), + WS_SUCCESS); + AssertIntEQ(RunKexInitWithError(WS_WINDOW_FULL, KEXINIT_REPLY_SENT), + WS_SUCCESS); + AssertIntEQ(RunKexInitWithError(WS_CHAN_RXD, KEXINIT_REPLY_SENT), + WS_SUCCESS); + AssertIntEQ(RunKexInitWithError(WS_WANT_WRITE, KEXINIT_REPLY_SENT), + WS_SUCCESS); + /* A blocked reply is still reported. */ + AssertIntEQ(RunKexInitWithError(WS_SUCCESS, KEXINIT_REPLY_BLOCKED), + WS_WANT_WRITE); + AssertIntEQ(RunKexInitWithError(WS_WINDOW_FULL, KEXINIT_REPLY_BLOCKED), + WS_WANT_WRITE); + AssertIntEQ(RunKexInitWithError(WS_WANT_WRITE, KEXINIT_REPLY_BLOCKED), + WS_WANT_WRITE); + /* So is a failed one. */ + AssertIntEQ(RunKexInitWithError(WS_SUCCESS, KEXINIT_REPLY_FAILED), + WS_SOCKET_ERROR_E); + /* No reply is owed once the server has sent its own KEXINIT. */ + AssertIntEQ(RunKexInitWithError(WS_WINDOW_FULL, KEXINIT_REPLY_NONE), + WS_SUCCESS); + AssertIntEQ(RunKexInitWithError(WS_WANT_WRITE, KEXINIT_REPLY_NONE), + WS_SUCCESS); +} +#endif /* NO_WOLFSSH_SERVER */ + /* Run a KEXINIT whose KEX-algorithms field is kexList through DoKexInit, * against a local list of FPF_KEX_GOOD. A list of unknown tokens fails to * match (WS_MATCH_KEX_ALGO_E); one rejected by the caps returns WS_BUFFER_E. @@ -18430,6 +18541,9 @@ int main(int argc, char** argv) && !defined(WOLFSSH_NO_RSA_SHA2_256) TestFirstPacketFollows(); TestKexInitReservedNonZeroRejected(); +#ifndef NO_WOLFSSH_SERVER + TestKexInitIgnoresStaleError(); +#endif TestKexInitNameListCaps(); TestKexInitEmptyName(); TestExtInfoEmptyName();