diff --git a/src/internal.c b/src/internal.c index eec2b1f06..7e13eb4ec 100644 --- a/src/internal.c +++ b/src/internal.c @@ -491,6 +491,9 @@ const char* GetErrorString(int err) case WS_KDF_E: return "KDF error"; + case WS_DISCONNECT: + return "peer sent disconnect"; + default: return "Unknown error code"; } @@ -5758,6 +5761,9 @@ static int KeyAgree_client(WOLFSSH* ssh, byte hashId, const byte* f, word32 fSz) } +static INLINE byte SigTypeForId(byte id); + + static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) { struct wolfSSH_sigKeyBlock *sigKeyBlock_ptr = NULL; @@ -6007,9 +6013,10 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) #ifndef WOLFSSH_NO_RSA int tmpIdx = begin - sigSz; #endif - /* Skip past the sig name. Check it, though. Other SSH - * implementations do the verify based on the name, despite what - * was agreed upon. XXX*/ + const char* expectedSigName = + IdToName(SigTypeForId(ssh->handshake->pubKeyId)); + word32 expectedSigNameSz = (word32)WSTRLEN(expectedSigName); + begin = 0; ret = GetUint32(&scratch, sig, sigSz, &begin); if (ret == WS_SUCCESS) { @@ -6020,6 +6027,16 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) ret = WS_PARSE_E; } } + if (ret == WS_SUCCESS) { + if (scratch != expectedSigNameSz || + WMEMCMP(sig + begin, expectedSigName, scratch) != 0) { + WLOG(WS_LOG_DEBUG, + "signature name %.*s did not match negotiated %s", + (int)scratch, (const char*)(sig + begin), + expectedSigName); + ret = WS_PARSE_E; + } + } if (ret == WS_SUCCESS) { begin += scratch; ret = GetUint32(&scratch, sig, sigSz, &begin); @@ -6475,7 +6492,6 @@ static int DoDisconnect(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) const char* reasonStr = NULL; word32 begin = *idx; - WOLFSSH_UNUSED(ssh); WOLFSSH_UNUSED(len); WOLFSSH_UNUSED(reasonStr); @@ -6524,7 +6540,8 @@ static int DoDisconnect(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) *idx = begin; - return WS_SUCCESS; + ssh->error = WS_DISCONNECT; + return WS_DISCONNECT; } @@ -8601,6 +8618,7 @@ static int DoChannelOpen(WOLFSSH* ssh, int isDirect = 0; #endif /* WOLFSSH_FWD */ WOLFSSH_CHANNEL* newChannel = NULL; + byte channelAppended = 0; int ret = WS_SUCCESS; word32 fail_reason = OPEN_OK; @@ -8695,9 +8713,13 @@ static int DoChannelOpen(WOLFSSH* ssh, } } #endif /* WOLFSSH_FWD */ - ChannelAppend(ssh, newChannel); - - ssh->clientState = CLIENT_CHANNEL_OPEN_DONE; + if (ret == WS_SUCCESS) { + ret = ChannelAppend(ssh, newChannel); + if (ret == WS_SUCCESS) { + channelAppended = 1; + ssh->clientState = CLIENT_CHANNEL_OPEN_DONE; + } + } } } @@ -8707,19 +8729,24 @@ static int DoChannelOpen(WOLFSSH* ssh, else { const char *description = NULL; - if (fail_reason == OPEN_ADMINISTRATIVELY_PROHIBITED) + if (newChannel != NULL && !channelAppended) { + ChannelDelete(newChannel, ssh->ctx->heap); + newChannel = NULL; + } + + if (fail_reason == OPEN_OK) { + fail_reason = OPEN_ADMINISTRATIVELY_PROHIBITED; + description = "Channel open failed."; + } + else if (fail_reason == OPEN_ADMINISTRATIVELY_PROHIBITED) description = "Administratively prohibited."; else if (fail_reason == OPEN_UNKNOWN_CHANNEL_TYPE) description = "Channel type not supported."; else if (fail_reason == OPEN_RESOURCE_SHORTAGE) description = "Not enough resources."; - if (description != NULL) { - ret = SendChannelOpenFail(ssh, peerChannelId, - fail_reason, description, "en"); - } - else - ret = SendRequestSuccess(ssh, 0); /* XXX Is this right? */ + ret = SendChannelOpenFail(ssh, peerChannelId, + fail_reason, description, "en"); } #ifdef WOLFSSH_FWD @@ -10566,7 +10593,6 @@ static int PreparePacket(WOLFSSH* ssh, word32 payloadSz) return ret; } - static int BundlePacket(WOLFSSH* ssh) { byte* output = NULL; @@ -17535,7 +17561,7 @@ int wolfSSH_oct2dec(WOLFSSH* ssh, byte* oct, word32 octSz) for (i = 0; i < octSz; i++) { - if (oct[i] < '0' || oct[0] > '7') { + if (oct[i] < '0' || oct[i] > '7') { ret = WS_BAD_ARGUMENT; break; } diff --git a/src/keygen.c b/src/keygen.c index 6ecc06f5c..cad01ed4d 100644 --- a/src/keygen.c +++ b/src/keygen.c @@ -189,7 +189,8 @@ int wolfSSH_MakeEcdsaKey(byte* out, word32 outSz, word32 size) int wolfSSH_MakeEd25519Key(byte* out, word32 outSz, word32 size) { -#ifndef WOLFSSH_NO_ED25519 +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) int ret = WS_SUCCESS; WC_RNG rng; diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 01280df2e..2a5f60ded 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -4458,8 +4458,8 @@ int SFTP_RemoveHandleNode(WOLFSSH* ssh, byte* handle, word32 handleSz) cur->prev->next = cur->next; } - if (cur->next == NULL && cur->prev == NULL) { - ssh->handleList = NULL; + if (cur == ssh->handleList) { + ssh->handleList = cur->next; } WFREE(cur, ssh->ctx->heap, DYNTYPE_SFTP); @@ -4513,7 +4513,7 @@ static int SFTP_FreeHandles(WOLFSSH* ssh) /* mktime() expects month from 0 to 11. Nucleus months * are saved as 1 to 12. Hence 1 is being deducted to * make it compatible with Unix time stamp. */ - #define WS_GETMON(d) (_GETMON(d) - 5) + #define WS_GETMON(d) (_GETMON(d) - 1) #define WS_GETHOUR(t) (_GETHOUR(t) - 1) #else #define WS_GETMON(d) _GETMON(d) @@ -4535,6 +4535,13 @@ static word32 TimeTo32(word16 d, word16 t) return mktime(&tmp); } + +#ifdef WOLFSSH_TEST_INTERNAL +int wolfSSH_TestNucleusMonthFromDate(word16 d) +{ + return (int)WS_GETMON(d); +} +#endif #endif /* NO_WOLFSSH_MKTIME */ diff --git a/tests/regress.c b/tests/regress.c index bc406a63b..be0eab3d9 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -124,6 +124,115 @@ static byte ParseMsgId(const byte* pkt, word32 sz) return pkt[5]; } +static word32 AppendByte(byte* buf, word32 bufSz, word32 idx, byte value) +{ + AssertTrue(idx < bufSz); + buf[idx++] = value; + return idx; +} + +static word32 AppendUint32(byte* buf, word32 bufSz, word32 idx, word32 value) +{ + word32 netValue = htonl(value); + + AssertTrue(idx + UINT32_SZ <= bufSz); + WMEMCPY(buf + idx, &netValue, UINT32_SZ); + idx += UINT32_SZ; + return idx; +} + +static word32 AppendData(byte* buf, word32 bufSz, word32 idx, + const byte* data, word32 dataSz) +{ + AssertTrue(idx + dataSz <= bufSz); + if (dataSz > 0) { + WMEMCPY(buf + idx, data, dataSz); + idx += dataSz; + } + return idx; +} + +static word32 AppendString(byte* buf, word32 bufSz, word32 idx, + const char* value) +{ + word32 valueSz = (word32)WSTRLEN(value); + + idx = AppendUint32(buf, bufSz, idx, valueSz); + return AppendData(buf, bufSz, idx, (const byte*)value, valueSz); +} + +static word32 WrapPacket(byte msgId, const byte* payload, word32 payloadSz, + byte* out, word32 outSz) +{ + word32 idx = 0; + word32 packetLen; + word32 need; + byte padLen = MIN_PAD_LENGTH; + + while (((UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ + payloadSz + padLen) % + MIN_BLOCK_SZ) != 0) { + padLen++; + } + + packetLen = PAD_LENGTH_SZ + MSG_ID_SZ + payloadSz + padLen; + need = UINT32_SZ + packetLen; + + AssertTrue(outSz >= need); + + idx = AppendUint32(out, outSz, idx, packetLen); + idx = AppendByte(out, outSz, idx, padLen); + idx = AppendByte(out, outSz, idx, msgId); + idx = AppendData(out, outSz, idx, payload, payloadSz); + AssertTrue(idx + padLen <= outSz); + WMEMSET(out + idx, 0, padLen); + idx += padLen; + + return idx; +} + +static word32 BuildChannelOpenPacket(const char* type, word32 peerChannelId, + word32 peerInitialWindowSz, word32 peerMaxPacketSz, + const byte* extra, word32 extraSz, byte* out, word32 outSz) +{ + byte payload[256]; + word32 idx = 0; + + idx = AppendString(payload, sizeof(payload), idx, type); + idx = AppendUint32(payload, sizeof(payload), idx, peerChannelId); + idx = AppendUint32(payload, sizeof(payload), idx, peerInitialWindowSz); + idx = AppendUint32(payload, sizeof(payload), idx, peerMaxPacketSz); + idx = AppendData(payload, sizeof(payload), idx, extra, extraSz); + + return WrapPacket(MSGID_CHANNEL_OPEN, payload, idx, out, outSz); +} + +static word32 BuildDisconnectPacket(word32 reason, byte* out, word32 outSz) +{ + byte payload[64]; + word32 idx = 0; + + idx = AppendUint32(payload, sizeof(payload), idx, reason); + idx = AppendUint32(payload, sizeof(payload), idx, 0); + idx = AppendUint32(payload, sizeof(payload), idx, 0); + + return WrapPacket(MSGID_DISCONNECT, payload, idx, out, outSz); +} + +#ifdef WOLFSSH_FWD +static word32 BuildDirectTcpipExtra(const char* host, word32 hostPort, + const char* origin, word32 originPort, byte* out, word32 outSz) +{ + word32 idx = 0; + + idx = AppendString(out, outSz, idx, host); + idx = AppendUint32(out, outSz, idx, hostPort); + idx = AppendString(out, outSz, idx, origin); + idx = AppendUint32(out, outSz, idx, originPort); + + return idx; +} +#endif + /* Simple in-memory transport harness */ typedef struct { byte* in; /* data to feed into client */ @@ -134,9 +243,6 @@ typedef struct { word32 outCap; } MemIo; -/* Minimal send/recv helpers for future transport-level tests; keep them static - * and unused for now to avoid warnings when Werror is on. */ -#ifdef WOLFSSH_TEST_MEMIO static int MemRecv(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) { (void)ssh; @@ -172,6 +278,671 @@ static void MemIoInit(MemIo* io, byte* in, word32 inSz, byte* out, word32 outCap io->outSz = 0; io->outCap = outCap; } + +typedef struct { + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + MemIo io; + byte out[256]; +} ChannelOpenHarness; + +static void InitChannelOpenHarness(ChannelOpenHarness* harness, + byte* in, word32 inSz) +{ + WMEMSET(harness, 0, sizeof(*harness)); + + harness->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(harness->ctx); + + wolfSSH_SetIORecv(harness->ctx, MemRecv); + wolfSSH_SetIOSend(harness->ctx, MemSend); + + harness->ssh = wolfSSH_new(harness->ctx); + AssertNotNull(harness->ssh); + + MemIoInit(&harness->io, in, inSz, harness->out, sizeof(harness->out)); + wolfSSH_SetIOReadCtx(harness->ssh, &harness->io); + wolfSSH_SetIOWriteCtx(harness->ssh, &harness->io); + harness->ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT; +} + +static void FreeChannelOpenHarness(ChannelOpenHarness* harness) +{ + if (harness->ssh != NULL) + wolfSSH_free(harness->ssh); + if (harness->ctx != NULL) + wolfSSH_CTX_free(harness->ctx); +} + +#if !defined(NO_WOLFSSH_SERVER) && !defined(NO_WOLFSSH_CLIENT) && \ + !defined(WOLFSSH_NO_RSA) && !defined(NO_FILESYSTEM) + #if !defined(WOLFSSH_NO_DH_GROUP14_SHA256) + #define KEXDH_REPLY_REGRESS_KEX_ALGO "diffie-hellman-group14-sha256" + #elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512) + #define KEXDH_REPLY_REGRESS_KEX_ALGO "diffie-hellman-group16-sha512" + #elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1) + #define KEXDH_REPLY_REGRESS_KEX_ALGO "diffie-hellman-group14-sha1" + #elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1) + #define KEXDH_REPLY_REGRESS_KEX_ALGO "diffie-hellman-group1-sha1" + #endif +#endif + +#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_USERNAME "jill" +#define REGRESS_PASSWORD "upthehill" +#define REGRESS_MAX_HANDSHAKE_STEPS 2048 +#define REGRESS_SSH_PROTO_PREFIX "SSH-" +#define REGRESS_SSH_PROTO_PREFIX_SZ 4U + +typedef struct { + byte data[REGRESS_DUPLEX_QUEUE_SZ]; + word32 len; +} DuplexQueue; + +typedef struct { + byte enabled; + int parseError; + word32 matchedPackets; + word32 mutatedPackets; + byte scratch[REGRESS_MUTATION_SCRATCH_SZ]; + word32 scratchSz; +} KexReplyMutator; + +typedef struct DuplexEndpoint { + DuplexQueue inbound; + struct DuplexEndpoint* peer; + KexReplyMutator* mutator; + byte isServer; +} DuplexEndpoint; + +typedef struct { + WOLFSSH_CTX* clientCtx; + WOLFSSH_CTX* serverCtx; + WOLFSSH* client; + WOLFSSH* server; + DuplexEndpoint clientIo; + DuplexEndpoint serverIo; + KexReplyMutator mutator; +} KexReplyHarness; + +typedef struct { + int clientRet; + int clientErr; + int serverRet; + int serverErr; + int clientSuccess; + int serverSuccess; + word32 steps; +} KexReplyRunResult; + +static word32 ReadUint32(const byte* buf) +{ + return ((word32)buf[0] << 24) | ((word32)buf[1] << 16) | + ((word32)buf[2] << 8) | (word32)buf[3]; +} + +static int ReadStringRef(word32* strSz, const byte** str, + const byte* buf, word32 len, word32* idx) +{ + if (strSz == NULL || str == NULL || buf == NULL || idx == NULL) { + return WS_BAD_ARGUMENT; + } + + if (*idx > len || len - *idx < LENGTH_SZ) { + return WS_PARSE_E; + } + + *strSz = ReadUint32(buf + *idx); + *idx += LENGTH_SZ; + if (*strSz > len - *idx) { + return WS_PARSE_E; + } + + *str = buf + *idx; + *idx += *strSz; + + return WS_SUCCESS; +} + +static word32 AppendBlob(byte* buf, word32 bufSz, word32 idx, + const byte* data, word32 dataSz) +{ + idx = AppendUint32(buf, bufSz, idx, dataSz); + return AppendData(buf, bufSz, idx, data, dataSz); +} + +static word32 LoadFileBuffer(const char* path, byte* buf, word32 bufSz) +{ + WFILE* file; + long fileSz; + word32 readSz; + + if (path == NULL || buf == NULL || bufSz == 0) { + return 0; + } + + if (WFOPEN(NULL, &file, path, "rb") != 0 || file == WBADFILE) { + return 0; + } + WFSEEK(NULL, file, 0, WSEEK_END); + fileSz = WFTELL(NULL, file); + WREWIND(NULL, file); + + if (fileSz <= 0 || (word32)fileSz > bufSz) { + WFCLOSE(NULL, file); + return 0; + } + + readSz = (word32)WFREAD(NULL, buf, 1, fileSz, file); + WFCLOSE(NULL, file); + + if (readSz != (word32)fileSz) { + return 0; + } + + return readSz; +} + +static int RegressionClientUserAuth(byte authType, + WS_UserAuthData* authData, void* ctx) +{ + static const char password[] = REGRESS_PASSWORD; + + (void)ctx; + + if (authType != WOLFSSH_USERAUTH_PASSWORD || authData == NULL) { + return WOLFSSH_USERAUTH_INVALID_AUTHTYPE; + } + + authData->sf.password.password = (byte*)password; + authData->sf.password.passwordSz = (word32)WSTRLEN(password); + + return WOLFSSH_USERAUTH_SUCCESS; +} + +static int RegressionServerUserAuth(byte authType, + WS_UserAuthData* authData, void* ctx) +{ + static const char password[] = REGRESS_PASSWORD; + word32 passwordSz = (word32)WSTRLEN(password); + + (void)ctx; + + if (authType != WOLFSSH_USERAUTH_PASSWORD || authData == NULL) { + return WOLFSSH_USERAUTH_FAILURE; + } + + if (authData->sf.password.password == NULL || + authData->sf.password.passwordSz != passwordSz) { + return WOLFSSH_USERAUTH_FAILURE; + } + + if (WMEMCMP(authData->sf.password.password, password, passwordSz) != 0) { + return WOLFSSH_USERAUTH_FAILURE; + } + + return WOLFSSH_USERAUTH_SUCCESS; +} + +static int AcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz, + void* ctx) +{ + (void)pubKey; + (void)pubKeySz; + (void)ctx; + + return 0; +} + +static int QueueAppend(DuplexQueue* queue, const byte* data, word32 dataSz) +{ + if (queue == NULL || data == NULL) { + return WS_BAD_ARGUMENT; + } + + if (dataSz > sizeof(queue->data) - queue->len) { + return WS_BUFFER_E; + } + + WMEMCPY(queue->data + queue->len, data, dataSz); + queue->len += dataSz; + + return WS_SUCCESS; +} + +static int RewriteSingleKexDhReplyPacket(const byte* packet, word32 packetSz, + const char* replacement, byte* out, word32 outSz, word32* outLen) +{ + const byte* payload; + const byte* pubKey; + const byte* f; + const byte* sigBlob; + const byte* sigName; + const byte* sigData; + word32 packetLen, padLen, payloadSz; + word32 pubKeySz, fSz, sigBlobSz; + word32 sigNameSz, sigDataSz; + word32 idx = 0; + word32 innerIdx = 0; + word32 outerIdx = 0; + word32 innerSigSz; + byte payloadBuf[REGRESS_MUTATION_SCRATCH_SZ]; + byte innerSig[REGRESS_MUTATION_SCRATCH_SZ]; + + if (replacement == NULL || out == NULL || outLen == NULL) { + return WS_BAD_ARGUMENT; + } + + if (packetSz < UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ) { + return 0; + } + + packetLen = ReadUint32(packet); + if (packetLen + UINT32_SZ != packetSz) { + return 0; + } + + padLen = packet[UINT32_SZ]; + if (packetLen < PAD_LENGTH_SZ + MSG_ID_SZ + padLen) { + return WS_PARSE_E; + } + + if (packet[UINT32_SZ + PAD_LENGTH_SZ] != MSGID_KEXDH_REPLY) { + return 0; + } + + payload = packet + UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ; + payloadSz = packetSz - UINT32_SZ - PAD_LENGTH_SZ - MSG_ID_SZ - padLen; + + if (ReadStringRef(&pubKeySz, &pubKey, payload, payloadSz, &idx) != + WS_SUCCESS) { + return WS_PARSE_E; + } + if (ReadStringRef(&fSz, &f, payload, payloadSz, &idx) != WS_SUCCESS) { + return WS_PARSE_E; + } + if (ReadStringRef(&sigBlobSz, &sigBlob, payload, payloadSz, &idx) != + WS_SUCCESS) { + return WS_PARSE_E; + } + + if (ReadStringRef(&sigNameSz, &sigName, sigBlob, sigBlobSz, &innerIdx) != + WS_SUCCESS) { + return WS_PARSE_E; + } + if (ReadStringRef(&sigDataSz, &sigData, sigBlob, sigBlobSz, &innerIdx) != + WS_SUCCESS) { + return WS_PARSE_E; + } + + if (innerIdx != sigBlobSz) { + return WS_PARSE_E; + } + + (void)sigName; + (void)sigNameSz; + + innerSigSz = 0; + innerSigSz = AppendString(innerSig, sizeof(innerSig), innerSigSz, + replacement); + innerSigSz = AppendBlob(innerSig, sizeof(innerSig), innerSigSz, + sigData, sigDataSz); + + outerIdx = 0; + outerIdx = AppendBlob(payloadBuf, sizeof(payloadBuf), outerIdx, + pubKey, pubKeySz); + outerIdx = AppendBlob(payloadBuf, sizeof(payloadBuf), outerIdx, f, fSz); + outerIdx = AppendBlob(payloadBuf, sizeof(payloadBuf), outerIdx, + innerSig, innerSigSz); + *outLen = WrapPacket(MSGID_KEXDH_REPLY, payloadBuf, outerIdx, out, outSz); + + return 1; +} + +static int RewriteKexDhReplySignatureName(const byte* packet, word32 packetSz, + const char* replacement, byte* out, word32 outSz, word32* outLen) +{ + word32 offset = 0; + + if (packet == NULL || replacement == NULL || out == NULL || outLen == NULL) { + return WS_BAD_ARGUMENT; + } + + while (packetSz - offset >= UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ) { + word32 curPacketSz = ReadUint32(packet + offset) + UINT32_SZ; + int rewriteRet; + + if (curPacketSz > packetSz - offset) { + return 0; + } + + if (packet[offset + UINT32_SZ + PAD_LENGTH_SZ] == MSGID_KEXDH_REPLY) { + rewriteRet = RewriteSingleKexDhReplyPacket(packet + offset, + curPacketSz, replacement, out, outSz, outLen); + if (rewriteRet <= 0) { + return rewriteRet; + } + + if (packetSz - offset - curPacketSz > outSz - *outLen) { + return WS_BUFFER_E; + } + + WMEMCPY(out + *outLen, packet + offset + curPacketSz, + packetSz - offset - curPacketSz); + *outLen += packetSz - offset - curPacketSz; + + return 1; + } + + offset += curPacketSz; + } + + return 0; +} + +static int DuplexRecv(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) +{ + DuplexEndpoint* endpoint = (DuplexEndpoint*)ctx; + word32 readSz; + + (void)ssh; + + if (endpoint == NULL || buf == NULL) { + return WS_CBIO_ERR_GENERAL; + } + + if (endpoint->inbound.len == 0) { + return WS_CBIO_ERR_WANT_READ; + } + + readSz = sz; + if (readSz > endpoint->inbound.len) { + readSz = endpoint->inbound.len; + } + + WMEMCPY(buf, endpoint->inbound.data, readSz); + endpoint->inbound.len -= readSz; + if (endpoint->inbound.len > 0) { + WMEMMOVE(endpoint->inbound.data, endpoint->inbound.data + readSz, + endpoint->inbound.len); + } + + return (int)readSz; +} + +static int DuplexSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) +{ + DuplexEndpoint* endpoint = (DuplexEndpoint*)ctx; + const byte* output = (const byte*)buf; + word32 outputSz = sz; + int ret; + + (void)ssh; + + if (endpoint == NULL || endpoint->peer == NULL || buf == NULL) { + return WS_CBIO_ERR_GENERAL; + } + + if (endpoint->isServer && endpoint->mutator != NULL && + endpoint->mutator->enabled && + endpoint->mutator->mutatedPackets == 0 && + outputSz >= UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ && + !(outputSz >= REGRESS_SSH_PROTO_PREFIX_SZ && + WMEMCMP(output, REGRESS_SSH_PROTO_PREFIX, + REGRESS_SSH_PROTO_PREFIX_SZ) == 0)) { + word32 mutatedSz = 0; + int mutateRet; + + mutateRet = RewriteKexDhReplySignatureName(output, outputSz, "ssh-rsa", + endpoint->mutator->scratch, + (word32)sizeof(endpoint->mutator->scratch), &mutatedSz); + if (mutateRet < 0) { + endpoint->mutator->parseError = mutateRet; + return WS_CBIO_ERR_GENERAL; + } + if (mutateRet > 0) { + endpoint->mutator->matchedPackets++; + endpoint->mutator->mutatedPackets++; + endpoint->mutator->scratchSz = mutatedSz; + output = endpoint->mutator->scratch; + outputSz = mutatedSz; + } + } + + ret = QueueAppend(&endpoint->peer->inbound, output, outputSz); + if (ret != WS_SUCCESS) { + return WS_CBIO_ERR_GENERAL; + } + + return (int)sz; +} + +static void InitDuplexPair(DuplexEndpoint* client, DuplexEndpoint* server, + KexReplyMutator* mutator) +{ + WMEMSET(client, 0, sizeof(*client)); + WMEMSET(server, 0, sizeof(*server)); + + client->peer = server; + server->peer = client; + server->mutator = mutator; + server->isServer = 1; +} + +static void FreeKexReplyHarness(KexReplyHarness* harness) +{ + if (harness->client != NULL) { + wolfSSH_free(harness->client); + } + if (harness->server != NULL) { + wolfSSH_free(harness->server); + } + if (harness->clientCtx != NULL) { + wolfSSH_CTX_free(harness->clientCtx); + } + if (harness->serverCtx != NULL) { + wolfSSH_CTX_free(harness->serverCtx); + } +} + +static void InitKexReplyHarness(KexReplyHarness* harness, + const char* keyAlgo, byte mutateReply) +{ + byte keyBuf[2048]; + word32 keySz; + + WMEMSET(harness, 0, sizeof(*harness)); + + InitDuplexPair(&harness->clientIo, &harness->serverIo, &harness->mutator); + harness->mutator.enabled = mutateReply; + + harness->clientCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(harness->clientCtx); + harness->serverCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(harness->serverCtx); + + AssertIntEQ(wolfSSH_CTX_SetAlgoListKex(harness->clientCtx, + KEXDH_REPLY_REGRESS_KEX_ALGO), WS_SUCCESS); + AssertIntEQ(wolfSSH_CTX_SetAlgoListKex(harness->serverCtx, + KEXDH_REPLY_REGRESS_KEX_ALGO), WS_SUCCESS); + AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(harness->clientCtx, keyAlgo), + WS_SUCCESS); + AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(harness->serverCtx, keyAlgo), + WS_SUCCESS); + + wolfSSH_SetIORecv(harness->clientCtx, DuplexRecv); + wolfSSH_SetIOSend(harness->clientCtx, DuplexSend); + wolfSSH_SetIORecv(harness->serverCtx, DuplexRecv); + wolfSSH_SetIOSend(harness->serverCtx, DuplexSend); + + wolfSSH_SetUserAuth(harness->clientCtx, RegressionClientUserAuth); + wolfSSH_SetUserAuth(harness->serverCtx, RegressionServerUserAuth); + wolfSSH_CTX_SetPublicKeyCheck(harness->clientCtx, AcceptAnyServerHostKey); + + keySz = LoadFileBuffer(REGRESS_SERVER_KEY_PATH, keyBuf, sizeof(keyBuf)); + AssertTrue(keySz > 0); + AssertIntEQ(wolfSSH_CTX_UsePrivateKey_buffer(harness->serverCtx, keyBuf, + keySz, WOLFSSH_FORMAT_ASN1), WS_SUCCESS); + + harness->client = wolfSSH_new(harness->clientCtx); + AssertNotNull(harness->client); + harness->server = wolfSSH_new(harness->serverCtx); + AssertNotNull(harness->server); + + wolfSSH_SetIOReadCtx(harness->client, &harness->clientIo); + wolfSSH_SetIOWriteCtx(harness->client, &harness->clientIo); + wolfSSH_SetIOReadCtx(harness->server, &harness->serverIo); + wolfSSH_SetIOWriteCtx(harness->server, &harness->serverIo); + + AssertIntEQ(wolfSSH_SetUsername(harness->client, REGRESS_USERNAME), + WS_SUCCESS); +} + +static int IsHandshakeRetryable(int err) +{ + return err == WS_WANT_READ || err == WS_WANT_WRITE || + err == WS_AUTH_PENDING; +} + +static void RunKexReplyHandshake(KexReplyHarness* harness, + KexReplyRunResult* result) +{ + word32 step; + + WMEMSET(result, 0, sizeof(*result)); + result->clientRet = WS_FATAL_ERROR; + result->serverRet = WS_FATAL_ERROR; + + for (step = 0; step < REGRESS_MAX_HANDSHAKE_STEPS; step++) { + if (!result->clientSuccess) { + result->clientRet = wolfSSH_connect(harness->client); + result->clientErr = wolfSSH_get_error(harness->client); + if (result->clientRet == WS_SUCCESS) { + result->clientSuccess = 1; + } + else if (!IsHandshakeRetryable(result->clientErr)) { + result->steps = step + 1; + return; + } + } + + if (!result->serverSuccess) { + result->serverRet = wolfSSH_accept(harness->server); + result->serverErr = wolfSSH_get_error(harness->server); + if (result->serverRet == WS_SUCCESS) { + result->serverSuccess = 1; + } + else if (!IsHandshakeRetryable(result->serverErr)) { + result->steps = step + 1; + return; + } + } + + if (result->clientSuccess && result->serverSuccess) { + result->steps = step + 1; + return; + } + } + + result->steps = REGRESS_MAX_HANDSHAKE_STEPS; +} + +static void AssertHandshakeSucceeds(const char* keyAlgo) +{ + KexReplyHarness harness; + KexReplyRunResult result; + + InitKexReplyHarness(&harness, keyAlgo, 0); + RunKexReplyHandshake(&harness, &result); + + AssertTrue(result.clientSuccess); + AssertTrue(result.serverSuccess); + AssertIntEQ(harness.mutator.mutatedPackets, 0); + AssertIntEQ(harness.client->connectState, CONNECT_SERVER_CHANNEL_REQUEST_DONE); + AssertIntEQ(harness.server->acceptState, ACCEPT_CLIENT_SESSION_ESTABLISHED); + + FreeKexReplyHarness(&harness); +} + +static void AssertHandshakeRejectsMutatedReply(const char* keyAlgo) +{ + KexReplyHarness harness; + KexReplyRunResult result; + + InitKexReplyHarness(&harness, keyAlgo, 1); + RunKexReplyHandshake(&harness, &result); + + AssertIntEQ(harness.mutator.parseError, 0); + AssertIntEQ(harness.mutator.matchedPackets, 1); + AssertIntEQ(harness.mutator.mutatedPackets, 1); + AssertFalse(result.clientSuccess); + AssertFalse(harness.client->connectState >= CONNECT_KEYED); + AssertTrue(result.clientRet == WS_FATAL_ERROR); + AssertTrue(result.clientErr != WS_WANT_READ && result.clientErr != WS_WANT_WRITE); + + FreeKexReplyHarness(&harness); +} + +#ifndef WOLFSSH_NO_RSA_SHA2_256 +static void TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade(void) +{ + AssertHandshakeSucceeds("rsa-sha2-256"); + AssertHandshakeRejectsMutatedReply("rsa-sha2-256"); +} +#endif + +#ifndef WOLFSSH_NO_RSA_SHA2_512 +static void TestKexDhReplyRejectsRsaSha2_512SigNameDowngrade(void) +{ + AssertHandshakeSucceeds("rsa-sha2-512"); + AssertHandshakeRejectsMutatedReply("rsa-sha2-512"); +} +#endif + +#endif /* KEXDH_REPLY_REGRESS_KEX_ALGO */ + +static void AssertChannelOpenFailResponse(const ChannelOpenHarness* harness, + int ret) +{ + byte msgId; + + AssertIntEQ(ret, WS_SUCCESS); + AssertIntEQ(harness->io.inOff, harness->io.inSz); + AssertTrue(harness->io.outSz > 0); + AssertTrue(harness->io.outSz <= harness->io.outCap); + + msgId = ParseMsgId(harness->io.out, harness->io.outSz); + AssertIntEQ(msgId, MSGID_CHANNEL_OPEN_FAIL); + AssertFalse(msgId == MSGID_REQUEST_FAILURE); + AssertIntEQ(harness->ssh->channelListSz, 0); + AssertTrue(harness->ssh->channelList == NULL); +} + +static int RejectChannelOpenCb(WOLFSSH_CHANNEL* channel, void* ctx) +{ + (void)channel; + (void)ctx; + + return WS_BAD_ARGUMENT; +} + +#ifdef WOLFSSH_FWD +static int RejectDirectTcpipSetup(WS_FwdCbAction action, void* ctx, + const char* host, word32 port) +{ + (void)ctx; + (void)host; + (void)port; + + if (action == WOLFSSH_FWD_LOCAL_SETUP) + return WS_FWD_SETUP_E; + + return WS_SUCCESS; +} #endif @@ -369,6 +1140,73 @@ static void TestChannelAllowedAfterAuth(WOLFSSH* ssh) AssertTrue(allowed); } +static void TestChannelOpenCallbackRejectSendsOpenFail(void) +{ + ChannelOpenHarness harness; + byte in[128]; + word32 inSz; + int ret; + + inSz = BuildChannelOpenPacket("session", 7, 0x4000, 0x8000, + NULL, 0, in, sizeof(in)); + + InitChannelOpenHarness(&harness, in, inSz); + AssertIntEQ(wolfSSH_CTX_SetChannelOpenCb(harness.ctx, RejectChannelOpenCb), + WS_SUCCESS); + + ret = DoReceive(harness.ssh); + AssertChannelOpenFailResponse(&harness, ret); + + FreeChannelOpenHarness(&harness); +} + +#ifdef WOLFSSH_FWD +static void TestDirectTcpipRejectSendsOpenFail(void) +{ + ChannelOpenHarness harness; + byte extra[128]; + byte in[192]; + word32 extraSz; + word32 inSz; + int ret; + + extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222, + extra, sizeof(extra)); + inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000, + extra, extraSz, in, sizeof(in)); + + InitChannelOpenHarness(&harness, in, inSz); + AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RejectDirectTcpipSetup, NULL), + WS_SUCCESS); + + ret = DoReceive(harness.ssh); + AssertChannelOpenFailResponse(&harness, ret); + + FreeChannelOpenHarness(&harness); +} +#endif + +#ifdef WOLFSSH_AGENT +static void TestAgentChannelNullAgentSendsOpenFail(void) +{ + ChannelOpenHarness harness; + byte in[128]; + word32 inSz; + int ret; + + inSz = BuildChannelOpenPacket("auth-agent@openssh.com", 11, 0x4000, + 0x8000, NULL, 0, in, sizeof(in)); + + InitChannelOpenHarness(&harness, in, inSz); + AssertTrue(harness.ssh->agent == NULL); + + ret = DoReceive(harness.ssh); + AssertChannelOpenFailResponse(&harness, ret); + + FreeChannelOpenHarness(&harness); +} +#endif + /* Reject a peer KEXINIT once keying is in progress. */ static void TestKexInitRejectedWhenKeying(WOLFSSH* ssh) @@ -383,6 +1221,100 @@ static void TestKexInitRejectedWhenKeying(WOLFSSH* ssh) AssertFalse(allowed); } +static void TestDisconnectSetsDisconnectError(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + MemIo io; + byte in[128]; + byte out[32]; + word32 inSz; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(ctx); + + wolfSSH_SetIORecv(ctx, MemRecv); + wolfSSH_SetIOSend(ctx, MemSend); + + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + + inSz = BuildDisconnectPacket(WOLFSSH_DISCONNECT_BY_APPLICATION, + in, sizeof(in)); + MemIoInit(&io, in, inSz, out, sizeof(out)); + wolfSSH_SetIOReadCtx(ssh, &io); + wolfSSH_SetIOWriteCtx(ssh, &io); + + ret = DoReceive(ssh); + AssertIntEQ(ret, WS_FATAL_ERROR); + AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT); + AssertIntEQ(io.inOff, io.inSz); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} + +#ifdef WOLFSSH_SFTP +static void TestOct2DecRejectsInvalidNonLeadingDigit(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + byte invalidOct[] = "0718"; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(ctx); + + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + + ret = wolfSSH_oct2dec(ssh, invalidOct, (word32)WSTRLEN((char*)invalidOct)); + AssertIntEQ(ret, WS_BAD_ARGUMENT); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} + +#ifdef WOLFSSH_STOREHANDLE +static void TestSftpRemoveHandleHeadUpdate(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + byte firstHandle[] = { 0x01, 0x02, 0x03, 0x04 }; + byte secondHandle[] = { 0x10, 0x20, 0x30, 0x40 }; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(ctx); + + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + + ret = SFTP_AddHandleNode(ssh, firstHandle, sizeof(firstHandle), "first"); + AssertIntEQ(ret, WS_SUCCESS); + + ret = SFTP_AddHandleNode(ssh, secondHandle, sizeof(secondHandle), "second"); + AssertIntEQ(ret, WS_SUCCESS); + + ret = SFTP_RemoveHandleNode(ssh, secondHandle, sizeof(secondHandle)); + AssertIntEQ(ret, WS_SUCCESS); + + AssertNotNull(ssh->handleList); + AssertTrue(ssh->handleList->prev == NULL); + AssertIntEQ(ssh->handleList->handleSz, (int)sizeof(firstHandle)); + AssertIntEQ(WMEMCMP(ssh->handleList->handle, firstHandle, + sizeof(firstHandle)), 0); + + ret = SFTP_RemoveHandleNode(ssh, firstHandle, sizeof(firstHandle)); + AssertIntEQ(ret, WS_SUCCESS); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} +#endif +#endif + /* Ensure client buffer cleanup tolerates multiple invocations after allocs. */ static void TestClientBuffersIdempotent(void) { @@ -554,6 +1486,13 @@ static void TestSftpBufferSendPendingOutput(void) wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); } +#if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) +static void TestNucleusMonthConversion(void) +{ + AssertIntEQ(wolfSSH_TestNucleusMonthFromDate((word16)(1U << 5)), 0); + AssertIntEQ(wolfSSH_TestNucleusMonthFromDate((word16)(12U << 5)), 11); +} +#endif #endif /* WOLFSSH_SFTP */ @@ -583,15 +1522,39 @@ int main(int argc, char** argv) TestPublicKeyFailureAborts(ssh); TestChannelBlockedBeforeAuth(ssh); TestChannelAllowedAfterAuth(ssh); + TestChannelOpenCallbackRejectSendsOpenFail(); +#ifdef WOLFSSH_FWD + TestDirectTcpipRejectSendsOpenFail(); +#endif +#ifdef WOLFSSH_AGENT + TestAgentChannelNullAgentSendsOpenFail(); +#endif TestKexInitRejectedWhenKeying(ssh); + TestDisconnectSetsDisconnectError(); TestClientBuffersIdempotent(); TestPasswordEofNoCrash(); #ifndef WOLFSSH_TEST_BLOCK TestWorkerReadsWhenSendWouldBlock(); #endif +#ifdef KEXDH_REPLY_REGRESS_KEX_ALGO + #ifndef WOLFSSH_NO_RSA_SHA2_256 + TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade(); + #endif + #ifndef WOLFSSH_NO_RSA_SHA2_512 + TestKexDhReplyRejectsRsaSha2_512SigNameDowngrade(); + #endif +#endif + #ifdef WOLFSSH_SFTP + TestOct2DecRejectsInvalidNonLeadingDigit(); + #ifdef WOLFSSH_STOREHANDLE + TestSftpRemoveHandleHeadUpdate(); + #endif TestSftpBufferSendPendingOutput(); + #if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) + TestNucleusMonthConversion(); + #endif #endif /* TODO: add app-level regressions that simulate stdin EOF/password diff --git a/tests/unit.c b/tests/unit.c index 59b0be364..03a09f364 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -31,7 +31,6 @@ #include #include #include -#include #define WOLFSSH_TEST_HEX2BIN #include @@ -265,7 +264,8 @@ static int test_EcdsaKeyGen(void) } #endif -#ifndef WOLFSSH_NO_ED25519 +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) static int test_Ed25519KeyGen(void) { int result = 0; @@ -338,7 +338,6 @@ static int test_Errors(void) return result; } - int wolfSSH_UnitTest(int argc, char** argv) { int testResult = 0, unitResult = 0; @@ -367,7 +366,8 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("EcdsaKeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif -#ifndef WOLFSSH_NO_ED25519 +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) unitResult = test_Ed25519KeyGen(); printf("Ed25519KeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; diff --git a/wolfssh/error.h b/wolfssh/error.h index 863fdd7e2..307173af6 100644 --- a/wolfssh/error.h +++ b/wolfssh/error.h @@ -136,8 +136,9 @@ enum WS_ErrorCodes { WS_ED25519_E = -1095, /* Ed25519 failure */ WS_AUTH_PENDING = -1096, /* User authentication still pending */ WS_KDF_E = -1097, /* KDF error*/ + WS_DISCONNECT = -1098, /* peer sent disconnect */ - WS_LAST_E = WS_KDF_E /* Update this to indicate last error */ + WS_LAST_E = WS_DISCONNECT /* Update this to indicate last error */ }; diff --git a/wolfssh/wolfsftp.h b/wolfssh/wolfsftp.h index f6c1add09..655c87427 100644 --- a/wolfssh/wolfsftp.h +++ b/wolfssh/wolfsftp.h @@ -285,6 +285,9 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void); #ifdef WOLFSSH_TEST_INTERNAL WOLFSSH_API int wolfSSH_TestSftpBufferSend(WOLFSSH* ssh, byte* data, word32 sz, word32 idx); + #if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) + WOLFSSH_API int wolfSSH_TestNucleusMonthFromDate(word16 d); + #endif #endif #ifdef __cplusplus @@ -292,4 +295,3 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void); #endif #endif /* _WOLFSSH_WOLFSFTP_H_ */ -