diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 0dddab945..0b76c1bb5 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -392,7 +392,7 @@ static void printKeyCompleteText(WOLFSSH* ssh, WS_Text id, const char* tag) size_t ret; ret = wolfSSH_GetText(ssh, id, str, strSz); - if (ret == strSz) { + if (ret >= strSz) { printf("\tString size was not large enough for %s\n", tag); } printf("\t%-30s : %s\n", tag, str); diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c index 15d57d946..470e08e06 100644 --- a/examples/sftpclient/sftpclient.c +++ b/examples/sftpclient/sftpclient.c @@ -667,7 +667,6 @@ static int doCmds(func_args* args) } if ((pt = WSTRNSTR(msg, "mkdir", sizeof(msg))) != NULL) { - WS_SFTP_FILEATRB atrb; int sz; char* f = NULL; @@ -691,7 +690,7 @@ static int doCmds(func_args* args) do { err = WS_SUCCESS; - if ((ret = wolfSSH_SFTP_MKDIR(ssh, pt, &atrb)) != WS_SUCCESS) { + if ((ret = wolfSSH_SFTP_MKDIR(ssh, pt, NULL)) != WS_SUCCESS) { err = wolfSSH_get_error(ssh); if (ret == WS_PERMISSIONS) { if (SFTP_FPUTS(args, "Insufficient permissions\n") < 0) { diff --git a/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c b/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c index bf2912cc1..eaff51f96 100644 --- a/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c +++ b/ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c @@ -353,7 +353,7 @@ static void printKeyCompleteText(WOLFSSH* ssh, WS_Text id, const char* tag) size_t ret; ret = wolfSSH_GetText(ssh, id, str, strSz); - if (ret == strSz) { + if (ret >= strSz) { printf("\tString size was not large enough for %s\n", tag); } printf("\t%-30s : %s\n", tag, str); diff --git a/src/internal.c b/src/internal.c index e840610f7..c771be2be 100644 --- a/src/internal.c +++ b/src/internal.c @@ -20071,6 +20071,10 @@ int SendIgnore(WOLFSSH* ssh, const unsigned char* data, word32 dataSz) if (ssh == NULL || (data == NULL && dataSz > 0)) ret = WS_BAD_ARGUMENT; + /* Also keeps the size sums in PreparePacket() from wrapping. */ + if (ret == WS_SUCCESS && dataSz > MAX_IGNORE_DATA_SZ) + ret = WS_BAD_ARGUMENT; + if (ret == WS_SUCCESS) ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + dataSz); @@ -24923,66 +24927,6 @@ int SendChannelSuccess(WOLFSSH* ssh, word32 channelId, int success) #if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP) -/* Checks if 'in' is absolute path, if not the returns the concat. of - * 'defaultPath' | 'in'. This leaves 'in' as-is and does not handle - * simplification of the path, such as removing ../ - * - * Sanity checks outSz and then adjusts it for the size used - * returns WS_SUCCESS on success - */ -int wolfSSH_GetPath(const char* defaultPath, byte* in, word32 inSz, - char* out, word32* outSz) -{ - word32 curSz = 0; - - if (out != NULL) { - WMEMSET(out, 0, *outSz); - } - - if (inSz == 0 || (!WOLFSSH_SFTP_IS_DELIM(in[0]) && - !WOLFSSH_SFTP_IS_WINPATH(inSz, in))) { - if (defaultPath != NULL) { - curSz = (word32)WSTRLEN(defaultPath); - if (out != NULL && curSz >= *outSz) { - return WS_INVALID_PATH_E; - } - if (out != NULL) { - WSTRNCPY(out, defaultPath, *outSz); - if (out[curSz] != '/') { - out[curSz] = '/'; - curSz++; - } - } - } - } - - if (out != NULL) { - if (curSz + inSz < *outSz) { - WMEMCPY(out + curSz, in, inSz); - } - } - curSz += inSz; - if (out != NULL) { - if (!WOLFSSH_SFTP_IS_DELIM(out[0])) { - if (curSz + 1 < *outSz) { - WMEMMOVE(out+1, out, curSz); - out[0] = '/'; - } - curSz++; - } - if (curSz < *outSz) { - out[curSz] = 0; - } - else { - return WS_BUFFER_E; - } - } - *outSz = curSz; - - return WS_SUCCESS; -} - - /* Cleans up absolute path, OS specific. * returns size of new path on success (strlen sz) and negative values on fail*/ int wolfSSH_CleanPath(WOLFSSH* ssh, char* in, int inSz) diff --git a/src/ssh.c b/src/ssh.c index e7346d63f..38b3c5355 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -1836,10 +1836,7 @@ int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz) byte scratch[128]; byte keyed; - WOLFSSH_UNUSED(buf); - WOLFSSH_UNUSED(bufSz); - - if (ssh == NULL) + if (ssh == NULL || (buf == NULL && bufSz > 0)) return WS_BAD_ARGUMENT; if (SendAfterDisconnect(ssh)) @@ -1853,6 +1850,9 @@ int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz) if (ssh->sendStrictKex && !keyed) return WS_INVALID_STATE_E; + if (buf != NULL) + return SendIgnore(ssh, buf, bufSz); + WMEMSET(scratch, 0, sizeof(scratch)); return SendIgnore(ssh, scratch, sizeof(scratch)); @@ -1992,13 +1992,15 @@ int wolfSSH_ChangeTerminalSize(WOLFSSH* ssh, word32 columns, word32 rows, void wolfSSH_SetTerminalResizeCb(WOLFSSH* ssh, WS_CallbackTerminalSize cb) { - ssh->termResizeCb = cb; + if (ssh != NULL) + ssh->termResizeCb = cb; } void wolfSSH_SetTerminalResizeCtx(WOLFSSH* ssh, void* usrCtx) { - ssh->termCtx = usrCtx; + if (ssh != NULL) + ssh->termCtx = usrCtx; } #endif @@ -2316,12 +2318,40 @@ static int DoSshPubKey(const byte* in, word32 inSz, byte** out, } +/* Points *newKey at the caller's *out when it holds keySz bytes, or at a new + * allocation when *out is NULL. */ +static int GetPubKeyOut(byte** out, word32 outSz, word32 keySz, + byte** newKey, void* heap) +{ + int ret = WS_SUCCESS; + + WOLFSSH_UNUSED(heap); + + if (*out == NULL) { + *newKey = (byte*)WMALLOC(keySz, heap, DYNTYPE_PRIVKEY); + if (*newKey == NULL) { + ret = WS_MEMORY_E; + } + } + else if (outSz < keySz) { + WLOG(WS_LOG_DEBUG, "DER public key output size too small"); + ret = WS_BUFFER_E; + } + else { + *newKey = *out; + } + + return ret; +} + + static int DoAsn1Key(const byte* in, word32 inSz, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, int isPrivate, void* heap) { int ret = WS_SUCCESS; byte* newKey = NULL; + word32 newKeySz = 0; WS_KeySignature* key = NULL; WOLFSSH_UNUSED(heap); @@ -2364,12 +2394,9 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, ret = WS_CRYPTO_FAILED; } else { - *outSz = LENGTH_SZ + nameLen + LENGTH_SZ + rawPubSz; - newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY); - if (newKey == NULL) { - ret = WS_MEMORY_E; - } - else { + newKeySz = LENGTH_SZ + nameLen + LENGTH_SZ + rawPubSz; + ret = GetPubKeyOut(out, *outSz, newKeySz, &newKey, heap); + if (ret == WS_SUCCESS) { c32toa(nameLen, &newKey[localIdx]); localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], name, nameLen); @@ -2378,7 +2405,7 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], rawPub, rawPubSz); *out = newKey; - ret = WS_SUCCESS; + *outSz = newKeySz; } } WFREE(rawPub, heap, DYNTYPE_PUBKEY); @@ -2426,13 +2453,10 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, ret = WS_CRYPTO_FAILED; } else { - *outSz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + curveNameSz + - LENGTH_SZ + qSz; - newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY); - if (newKey == NULL) { - ret = WS_MEMORY_E; - } - else { + newKeySz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + + curveNameSz + LENGTH_SZ + qSz; + ret = GetPubKeyOut(out, *outSz, newKeySz, &newKey, heap); + if (ret == WS_SUCCESS) { c32toa(*outTypeSz, &newKey[localIdx]); localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], *outType, *outTypeSz); @@ -2445,7 +2469,7 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], q, qSz); *out = newKey; - ret = WS_SUCCESS; + *outSz = newKeySz; } } WFREE(q, heap, DYNTYPE_PUBKEY); @@ -2465,12 +2489,9 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, ret = WS_CRYPTO_FAILED; } else { - *outSz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + qSz; - newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY); - if (newKey == NULL) { - ret = WS_MEMORY_E; - } - else { + newKeySz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + qSz; + ret = GetPubKeyOut(out, *outSz, newKeySz, &newKey, heap); + if (ret == WS_SUCCESS) { c32toa(*outTypeSz, &newKey[localIdx]); localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], *outType, *outTypeSz); @@ -2479,7 +2500,7 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, localIdx += LENGTH_SZ; WMEMCPY(&newKey[localIdx], q, qSz); *out = newKey; - ret = WS_SUCCESS; + *outSz = newKeySz; } } } @@ -2508,14 +2529,11 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, /* if MSB is set need leading zero */ nMsb = 1; } - *outSz = LENGTH_SZ + (word32)WSTRLEN(keyFormat) + + newKeySz = LENGTH_SZ + (word32)WSTRLEN(keyFormat) + LENGTH_SZ + eSz + LENGTH_SZ + nSz + nMsb; - newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY); - if (newKey == NULL) { - ret = WS_MEMORY_E; - } + ret = GetPubKeyOut(out, *outSz, newKeySz, &newKey, heap); } if (ret == WS_SUCCESS) { /* encode the key format string */ @@ -2539,6 +2557,7 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, WMEMCPY(&newKey[idx], n, nSz); *out = newKey; + *outSz = newKeySz; } } else @@ -5675,9 +5694,12 @@ static const char* MacNameForId(byte macid, byte cipherid) size_t wolfSSH_GetText(WOLFSSH *ssh, WS_Text id, char *str, size_t strSz) { - int ret = 0; - + const char* text = ""; + size_t textSz; #ifndef WOLFSSH_NO_DH + /* Big enough for the longest DH text with any int bit count. */ + char dhText[80]; + int dhGroup = 0; static const char standard_dh_format[] = "%d-bit Diffie-Hellman with standard group %d"; #endif @@ -5687,30 +5709,27 @@ size_t wolfSSH_GetText(WOLFSSH *ssh, WS_Text id, char *str, size_t strSz) switch (id) { case WOLFSSH_TEXT_KEX_HASH: - ret = WSNPRINTF(str, strSz, "%s", HashNameForId(ssh->kexId)); + text = HashNameForId(ssh->kexId); break; case WOLFSSH_TEXT_KEX_CURVE: - ret = WSNPRINTF(str, strSz, "%s", CurveNameForId(ssh->kexId)); + text = CurveNameForId(ssh->kexId); break; case WOLFSSH_TEXT_CRYPTO_IN_CIPHER: - ret = WSNPRINTF(str, strSz, "%s", - CipherNameForId(ssh->peerEncryptId)); + text = CipherNameForId(ssh->peerEncryptId); break; case WOLFSSH_TEXT_CRYPTO_OUT_CIPHER: - ret = WSNPRINTF(str, strSz, "%s", CipherNameForId(ssh->encryptId)); + text = CipherNameForId(ssh->encryptId); break; case WOLFSSH_TEXT_CRYPTO_IN_MAC: - ret = WSNPRINTF(str, strSz, "%s", MacNameForId(ssh->peerMacId, - ssh->peerEncryptId)); + text = MacNameForId(ssh->peerMacId, ssh->peerEncryptId); break; case WOLFSSH_TEXT_CRYPTO_OUT_MAC: - ret = WSNPRINTF(str, strSz, "%s", MacNameForId(ssh->macId, - ssh->encryptId)); + text = MacNameForId(ssh->macId, ssh->encryptId); break; case WOLFSSH_TEXT_KEX_ALGO: @@ -5722,75 +5741,79 @@ size_t wolfSSH_GetText(WOLFSSH *ssh, WS_Text id, char *str, size_t strSz) case ID_CURVE25519_SHA256: case ID_CURVE25519_SHA256_LIBSSH: #endif - ret = WSNPRINTF(str, strSz, "%s", "ECDH"); + text = "ECDH"; break; #ifndef WOLFSSH_NO_NISTP256_MLKEM768_SHA256 case ID_NISTP256_MLKEM768_SHA256: - ret = WSNPRINTF(str, strSz, "%s", - "ECDH-NISTP256-MLKEM768"); + text = "ECDH-NISTP256-MLKEM768"; break; #endif #ifndef WOLFSSH_NO_NISTP384_MLKEM1024_SHA384 case ID_NISTP384_MLKEM1024_SHA384: - ret = WSNPRINTF(str, strSz, "%s", - "ECDH-NISTP384-MLKEM1024"); + text = "ECDH-NISTP384-MLKEM1024"; break; #endif #ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256 case ID_CURVE25519_MLKEM768_SHA256: - ret = WSNPRINTF(str, strSz, "%s", - "ECDH-CURVE25519-MLKEM768"); + text = "ECDH-CURVE25519-MLKEM768"; break; #endif #ifndef WOLFSSH_NO_DH case ID_DH_GROUP1_SHA1: - ret = WSNPRINTF(str, strSz, standard_dh_format, - ssh->primeGroupSz*8, 1); + dhGroup = 1; break; case ID_DH_GROUP14_SHA1: case ID_DH_GROUP14_SHA256: - ret = WSNPRINTF(str, strSz, standard_dh_format, - ssh->primeGroupSz*8, 14); + dhGroup = 14; break; case ID_DH_GROUP16_SHA512: - ret = WSNPRINTF(str, strSz, standard_dh_format, - ssh->primeGroupSz*8, 16); + dhGroup = 16; break; case ID_DH_GEX_SHA256: - ret = WSNPRINTF(str, strSz, - "%d-bit Diffie-Hellman with server-supplied group", - ssh->primeGroupSz*8); + if (WSNPRINTF(dhText, sizeof(dhText), + "%d-bit Diffie-Hellman with server-supplied group", + ssh->primeGroupSz*8) > 0) { + text = dhText; + } break; #endif /* !WOLFSSH_NO_DH */ case ID_EXT_INFO_S: - #if defined(__CCRX__) - ret = WSNPRINTF0(str, strSz, "Server extensions KEX"); - #else - ret = WSNPRINTF(str, strSz, "Server extensions KEX"); - #endif + text = "Server extensions KEX"; break; case ID_EXT_INFO_C: - #if defined(__CCRX__) - ret = WSNPRINTF0(str, strSz, "Client extensions KEX"); - #else - ret = WSNPRINTF(str, strSz, "Client extensions KEX"); - #endif + text = "Client extensions KEX"; break; - } + #ifndef WOLFSSH_NO_DH + if (dhGroup != 0 && WSNPRINTF(dhText, sizeof(dhText), + standard_dh_format, ssh->primeGroupSz*8, dhGroup) > 0) { + text = dhText; + } + #endif break; } - return ret < 0 ? 0 : (size_t)ret; + /* Format locally and copy, so truncation reads the same whichever + * function WSNPRINTF maps to. */ + textSz = WSTRLEN(text); + if (textSz < strSz) { + WMEMCPY(str, text, textSz + 1); + } + else { + WMEMCPY(str, text, strSz - 1); + str[strSz - 1] = '\0'; + } + + return textSz; } void wolfSSH_SetKeyingCompletionCb(WOLFSSH_CTX* ctx, WS_CallbackKeyingCompletion cb) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 0f91e0dc7..ac7d9af3f 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -9032,6 +9032,7 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) { struct WS_SFTP_MKDIR_STATE* state; + WS_SFTP_FILEATRB defAtr; int ret; byte type; @@ -9058,13 +9059,23 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) if (wolfSSH_SFTP_buffer_size(&state->buffer) == 0) { /* packet not created yet */ int sz = (int)WSTRLEN(dir); + int atrSz; + + if (atr == NULL) { + WMEMSET(&defAtr, 0, sizeof(defAtr)); + defAtr.flags = WOLFSSH_FILEATRB_PERM; + defAtr.per = 0777; + atr = &defAtr; + } + atrSz = SFTP_AttributesSz(ssh, atr); + if (wolfSSH_SFTP_buffer_create(ssh, &state->buffer, sz + - WOLFSSH_SFTP_HEADER + UINT32_SZ * 3) != 0) { + WOLFSSH_SFTP_HEADER + UINT32_SZ + atrSz) != 0) { return WS_MEMORY_E; } if (SFTP_SetHeader(ssh, ssh->reqId, WOLFSSH_FTP_MKDIR, - sz + UINT32_SZ * 3, + sz + UINT32_SZ + atrSz, wolfSSH_SFTP_buffer_data(&state->buffer)) != WS_SUCCESS) { return WS_FATAL_ERROR; } @@ -9078,11 +9089,11 @@ int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) wolfSSH_SFTP_buffer_seek(&state->buffer, wolfSSH_SFTP_buffer_idx(&state->buffer), sz); - wolfSSH_SFTP_buffer_c32toa(&state->buffer, UINT32_SZ); - - /* @TODO handle setting attributes */ - WOLFSSH_UNUSED(atr); - wolfSSH_SFTP_buffer_c32toa(&state->buffer, 0777); + SFTP_SetAttributes(ssh, + wolfSSH_SFTP_buffer_data(&state->buffer) + + wolfSSH_SFTP_buffer_idx(&state->buffer), atrSz, atr); + wolfSSH_SFTP_buffer_seek(&state->buffer, + wolfSSH_SFTP_buffer_idx(&state->buffer), atrSz); ret = wolfSSH_SFTP_buffer_set_size(&state->buffer, wolfSSH_SFTP_buffer_idx(&state->buffer)); diff --git a/tests/api.c b/tests/api.c index 8904884e6..aac1b9a09 100644 --- a/tests/api.c +++ b/tests/api.c @@ -46,6 +46,7 @@ #include #include #include +#include #ifdef WOLFSSH_SCP #include #endif @@ -2531,6 +2532,261 @@ static void test_wolfSSH_ReadPublicKey_pem(void) } +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519_KEY_IMPORT) && \ + defined(HAVE_ED25519_KEY_EXPORT) + #define API_READKEY_ED25519_PUB +#endif +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && defined(WC_ENABLE_ASYM_KEY_EXPORT) + #define API_READKEY_MLDSA_PUB +#endif + +#if (!defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_TEST_INTERNAL)) || \ + (!defined(WOLFSSH_NO_ECDSA) && defined(HAVE_ECC_KEY_EXPORT)) || \ + defined(API_READKEY_ED25519_PUB) || defined(API_READKEY_MLDSA_PUB) +/* Reads the public DER key twice, into a new allocation and into a caller + * buffer, and expects the same blob in the caller's buffer. */ +static void ReadKeyAsn1PubCallerBuf(const byte* der, word32 derSz) +{ + byte buf[2048]; + byte* alloc = NULL; + word32 allocSz = 0; + byte* key; + word32 keySz; + const byte* keyType = NULL; + word32 keyTypeSz = 0; + + AssertIntEQ(wolfSSH_ReadKey_buffer_ex(der, derSz, WOLFSSH_FORMAT_ASN1, + &alloc, &allocSz, &keyType, &keyTypeSz, 0, NULL), WS_SUCCESS); + AssertNotNull(alloc); + AssertIntGT(allocSz, 0); + AssertIntLE(allocSz, (word32)sizeof(buf)); + + key = buf; + keySz = (word32)sizeof(buf); + WMEMSET(buf, 0, sizeof(buf)); + AssertIntEQ(wolfSSH_ReadKey_buffer_ex(der, derSz, WOLFSSH_FORMAT_ASN1, + &key, &keySz, &keyType, &keyTypeSz, 0, NULL), WS_SUCCESS); + AssertTrue(key == buf); + AssertIntEQ(keySz, allocSz); + AssertIntEQ(WMEMCMP(buf, alloc, allocSz), 0); + + /* One byte short is refused, and the caller's buffer stays put. */ + key = buf; + keySz = allocSz - 1; + AssertIntEQ(wolfSSH_ReadKey_buffer_ex(der, derSz, WOLFSSH_FORMAT_ASN1, + &key, &keySz, &keyType, &keyTypeSz, 0, NULL), WS_BUFFER_E); + AssertTrue(key == buf); + AssertIntEQ(keySz, allocSz - 1); + + WFREE(alloc, NULL, DYNTYPE_PRIVKEY); +} +#endif + + +/* An ASN.1 public key read fills a caller-supplied output buffer instead of + * replacing it with a new allocation. */ +static void test_wolfSSH_ReadKey_asn1PubCallerBuf(void) +{ +#if !defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_TEST_INTERNAL) + { + byte* der = NULL; + word32 derSz = 0; + + AssertIntEQ(ConvertHexToBin(serverKeyRsaPubDer, &der, &derSz, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL), 0); + ReadKeyAsn1PubCallerBuf(der, derSz); + WFREE(der, NULL, 0); + } +#endif +#if !defined(WOLFSSH_NO_ECDSA) && defined(HAVE_ECC_KEY_EXPORT) + { + ecc_key ecc; + byte* privDer = NULL; + word32 privDerSz = 0; + byte pubDer[256]; + word32 idx = 0; + int pubDerSz; + + AssertIntEQ(ConvertHexToBin(serverKeyEccDer, &privDer, &privDerSz, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL), 0); + AssertIntEQ(wc_ecc_init(&ecc), 0); + AssertIntEQ(wc_EccPrivateKeyDecode(privDer, &idx, &ecc, privDerSz), + 0); + pubDerSz = wc_EccPublicKeyToDer(&ecc, pubDer, sizeof(pubDer), 1); + wc_ecc_free(&ecc); + AssertIntGT(pubDerSz, 0); + ReadKeyAsn1PubCallerBuf(pubDer, (word32)pubDerSz); + WFREE(privDer, NULL, 0); + } +#endif +#ifdef API_READKEY_ED25519_PUB + { + ed25519_key ed; + byte* privDer = NULL; + word32 privDerSz = 0; + byte pubDer[128]; + word32 idx = 0; + int pubDerSz; + + AssertIntEQ(ConvertHexToBin(serverKeyEd25519Der, &privDer, &privDerSz, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL), 0); + AssertIntEQ(wc_ed25519_init(&ed), 0); + AssertIntEQ(wc_Ed25519PrivateKeyDecode(privDer, &idx, &ed, privDerSz), + 0); + pubDerSz = wc_Ed25519PublicKeyToDer(&ed, pubDer, sizeof(pubDer), 1); + wc_ed25519_free(&ed); + AssertIntGT(pubDerSz, 0); + ReadKeyAsn1PubCallerBuf(pubDer, (word32)pubDerSz); + WFREE(privDer, NULL, 0); + } +#endif +#ifdef API_READKEY_MLDSA_PUB + { + MlDsaKey mlKey; + WC_RNG rng; + byte* pubDer; + int pubDerSz; + + AssertIntEQ(wc_MlDsaKey_Init(&mlKey, NULL, INVALID_DEVID), 0); + AssertIntEQ(wc_MlDsaKey_SetParams(&mlKey, WC_ML_DSA_44), 0); + AssertIntEQ(wc_InitRng(&rng), 0); + AssertIntEQ(wc_MlDsaKey_MakeKey(&mlKey, &rng), 0); + wc_FreeRng(&rng); + pubDer = (byte*)WMALLOC(WC_MLDSA_44_PUB_KEY_DER_SIZE, NULL, 0); + AssertNotNull(pubDer); + pubDerSz = wc_MlDsaKey_PublicKeyToDer(&mlKey, pubDer, + WC_MLDSA_44_PUB_KEY_DER_SIZE, 1); + wc_MlDsaKey_Free(&mlKey); + AssertIntGT(pubDerSz, 0); + ReadKeyAsn1PubCallerBuf(pubDer, (word32)pubDerSz); + WFREE(pubDer, NULL, 0); + } +#endif +} + + +#ifdef WOLFSSH_TERM +static int TermResizeNopCb(WOLFSSH* ssh, word32 columns, word32 rows, + word32 widthPixels, word32 heightPixels, void* ctx) +{ + WOLFSSH_UNUSED(ssh); + WOLFSSH_UNUSED(columns); + WOLFSSH_UNUSED(rows); + WOLFSSH_UNUSED(widthPixels); + WOLFSSH_UNUSED(heightPixels); + WOLFSSH_UNUSED(ctx); + return WS_SUCCESS; +} +#endif + +/* The terminal resize setters treat a NULL session as a no-op. */ +static void test_wolfSSH_SetTerminalResize(void) +{ +#ifdef WOLFSSH_TERM + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + int marker = 0; + + wolfSSH_SetTerminalResizeCb(NULL, TermResizeNopCb); + wolfSSH_SetTerminalResizeCtx(NULL, &marker); + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(ctx); + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + wolfSSH_SetTerminalResizeCb(ssh, TermResizeNopCb); + wolfSSH_SetTerminalResizeCtx(ssh, &marker); + AssertTrue(ssh->termResizeCb == TermResizeNopCb); + AssertTrue(ssh->termCtx == (void*)&marker); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +#endif +} + + +/* wolfSSH_GetText() always terminates its output and returns the full text + * length, so a return of strSz or more flags truncation on every build. */ +static void test_wolfSSH_GetText(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + char str[80]; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(ctx); + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + + AssertIntEQ(wolfSSH_GetText(NULL, WOLFSSH_TEXT_KEX_ALGO, str, + sizeof(str)), 0); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, NULL, + sizeof(str)), 0); + str[0] = 'X'; + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, 0), 0); + AssertIntEQ(str[0], 'X'); + + /* Nothing negotiated yet, and an unknown id: an empty string. */ + ssh->kexId = ID_NONE; + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, + sizeof(str)), 0); + AssertIntEQ(str[0], '\0'); + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, (WS_Text)0x7F, str, sizeof(str)), 0); + AssertIntEQ(str[0], '\0'); + + /* Fits, fits exactly, and truncated. */ + ssh->kexId = ID_ECDH_SHA2_NISTP256; + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, + sizeof(str)), 4); + AssertStrEQ(str, "ECDH"); + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, 5), 4); + AssertStrEQ(str, "ECDH"); + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, 3), 4); + AssertStrEQ(str, "EC"); + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, 1), 4); + AssertIntEQ(str[0], '\0'); + +#ifndef WOLFSSH_NO_DH + { + static const char gexText[] = + "2048-bit Diffie-Hellman with server-supplied group"; + static const char g14Text[] = + "2048-bit Diffie-Hellman with standard group 14"; + + ssh->primeGroupSz = 256; + ssh->kexId = ID_DH_GEX_SHA256; + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, + sizeof(str)), WSTRLEN(gexText)); + AssertStrEQ(str, gexText); + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, 9), + WSTRLEN(gexText)); + AssertStrEQ(str, "2048-bit"); + + ssh->kexId = ID_DH_GROUP14_SHA256; + WMEMSET(str, 'X', sizeof(str)); + AssertIntEQ(wolfSSH_GetText(ssh, WOLFSSH_TEXT_KEX_ALGO, str, + sizeof(str)), WSTRLEN(g14Text)); + AssertStrEQ(str, g14Text); + } +#endif + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} + + #if defined(WOLFSSH_TPM) && !defined(NO_FILESYSTEM) && \ !defined(NO_WRITE_TEMP_FILES) && !defined(WOLFSSH_USER_FILESYSTEM) @@ -7258,8 +7514,94 @@ static void test_wolfSSH_SFTP_GetResume(void) } +/* MKDIR sends the caller's attributes: the in-process echoserver creates the + * directory with the requested permissions. */ +static void test_wolfSSH_SFTP_MKDIR_attrs(void) +{ +#if !defined(USE_WINDOWS_API) && !defined(WOLFSSH_FATFS) && \ + !defined(WOLFSSH_ZEPHYR) + func_args ser; + tcp_ready ready; + int argsCount; + WS_SOCKET_T clientFd; + + const char* args[10]; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + + THREAD_TYPE serThread; + + WS_SFTP_FILEATRB atr; + char dirName[48]; + + /* pid-stamped so concurrent runs in one tree don't share it */ + WSNPRINTF(dirName, sizeof(dirName), "wolfssh_14634_mkdir_%d.tmp", + (int)getpid()); + WMEMSET(&ser, 0, sizeof(func_args)); + + argsCount = 0; + args[argsCount++] = "."; + args[argsCount++] = "-1"; + args[argsCount++] = "-p"; + args[argsCount++] = "0"; + ser.argv = (char**)args; + ser.argc = argsCount; + ser.signal = &ready; + InitTcpReady(ser.signal); + ThreadStart(echoserver_test, (void*)&ser, &serThread); + WaitTcpReady(&ready); + + sftp_client_connect(&ctx, &ssh, ready.port); + AssertNotNull(ctx); + AssertNotNull(ssh); + + (void)wolfSSH_SFTP_RMDIR(ssh, dirName); /* normally absent */ + + /* A private directory, which the fixed 0777 request never produced. */ + WMEMSET(&atr, 0, sizeof(atr)); + atr.flags = WOLFSSH_FILEATRB_PERM; + atr.per = 0700; + AssertIntEQ(wolfSSH_SFTP_MKDIR(ssh, dirName, &atr), WS_SUCCESS); + WMEMSET(&atr, 0, sizeof(atr)); + AssertIntEQ(wolfSSH_SFTP_STAT(ssh, dirName, &atr), WS_SUCCESS); + AssertTrue((atr.flags & WOLFSSH_FILEATRB_PERM) != 0); + AssertIntEQ(atr.per & 0777, 0700); + AssertIntEQ(wolfSSH_SFTP_RMDIR(ssh, dirName), WS_SUCCESS); + + /* A NULL atr still makes a directory the owner can use. */ + AssertIntEQ(wolfSSH_SFTP_MKDIR(ssh, dirName, NULL), WS_SUCCESS); + WMEMSET(&atr, 0, sizeof(atr)); + AssertIntEQ(wolfSSH_SFTP_STAT(ssh, dirName, &atr), WS_SUCCESS); + AssertIntEQ(atr.per & 0700, 0700); + AssertIntEQ(wolfSSH_SFTP_RMDIR(ssh, dirName), WS_SUCCESS); + + /* take care of re-keying state before shutdown call */ + while (wolfSSH_get_error(ssh) == WS_REKEYING) { + wolfSSH_worker(ssh, NULL); + } + + argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh)); +#if DEFAULT_HIGHWATER_MARK < 8000 + if (argsCount == WS_REKEYING) { + argsCount = WS_SUCCESS; + } +#endif + AssertIntEQ(argsCount, WS_SUCCESS); + + clientFd = wolfSSH_get_fd(ssh); + WCLOSESOCKET(clientFd); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + ThreadJoin(serThread); + FreeTcpReady(&ready); +#endif /* !USE_WINDOWS_API && !WOLFSSH_FATFS && !WOLFSSH_ZEPHYR */ +} + + #else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */ static void test_wolfSSH_SFTP_SendReadPacket(void) { ; } +static void test_wolfSSH_SFTP_MKDIR_attrs(void) { ; } static void test_wolfSSH_SFTP_PartialSend(void) { ; } static void test_wolfSSH_SFTP_ReKey(void) { ; } static void test_wolfSSH_SFTP_ReKey_NonBlock(void) { ; } @@ -9365,6 +9707,9 @@ int wolfSSH_ApiTest(int argc, char** argv) test_wolfSSH_CertMan(); test_wolfSSH_ReadKey(); test_wolfSSH_ReadPublicKey_pem(); + test_wolfSSH_ReadKey_asn1PubCallerBuf(); + test_wolfSSH_SetTerminalResize(); + test_wolfSSH_GetText(); test_wolfSSH_ReadKey_badPad(); #if defined(WOLFSSH_TPM) && !defined(NO_FILESYSTEM) && \ !defined(NO_WRITE_TEMP_FILES) && !defined(WOLFSSH_USER_FILESYSTEM) @@ -9464,6 +9809,7 @@ int wolfSSH_ApiTest(int argc, char** argv) test_wolfSSH_SFTP_SendPacketType_negotiation_non_block(); test_wolfSSH_SFTP_PutResume(); test_wolfSSH_SFTP_GetResume(); + test_wolfSSH_SFTP_MKDIR_attrs(); /* Either SCP or SFTP */ test_wolfSSH_RealPath(); diff --git a/tests/regress.c b/tests/regress.c index 24cc16182..bc850250f 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -730,7 +730,13 @@ static word32 LoadFileBuffer(const char* path, byte* buf, word32 bufSz) #ifdef KEXDH_REPLY_REGRESS_KEX_ALGO -#define REGRESS_DUPLEX_QUEUE_SZ 32768U +/* Holds a handshake flight, or one packet a little over MAX_PACKET_SZ so + * the peer is what judges its size. */ +#if MAX_PACKET_SZ + 256 > 32768 + #define REGRESS_DUPLEX_QUEUE_SZ ((word32)MAX_PACKET_SZ + 256U) +#else + #define REGRESS_DUPLEX_QUEUE_SZ 32768U +#endif #define REGRESS_MUTATION_SCRATCH_SZ 4096U #define REGRESS_SERVER_KEY_ECC_PATH "keys/server-key-ecc.der" #define REGRESS_SERVER_KEY_ED25519_PATH "keys/server-key-ed25519.der" @@ -3023,6 +3029,51 @@ static void PumpDuplexPair(KexReplyHarness* harness) Fail(("the rekey to finish"), ("it ran out of steps")); } +/* The largest IGNORE wolfSSH_SendIgnore() takes reaches a keyed wolfSSH + * peer intact, under the largest MAC where the build has it. */ +static void TestSendIgnoreLargestKeyed(void) +{ + KexReplyHarness harness; + KexReplyRunResult result; + byte* data; + word32 peerSeq; + + InitStrictKexHarness(&harness, MSGID_NONE, 1, 1); + /* A rekey mid-test would add packets and, under strict KEX, reset + * peerSeq; keep a build-time DEFAULT_HIGHWATER_MARK from firing one. */ + AssertIntEQ(wolfSSH_SetHighwater(harness.client, 0), WS_SUCCESS); + AssertIntEQ(wolfSSH_SetHighwater(harness.server, 0), WS_SUCCESS); +#if !defined(WOLFSSH_NO_AES_CTR) && !defined(WOLFSSH_NO_HMAC_SHA2_512) + AssertIntEQ(wolfSSH_SetAlgoListCipher(harness.client, "aes256-ctr"), + WS_SUCCESS); + AssertIntEQ(wolfSSH_SetAlgoListMac(harness.client, "hmac-sha2-512"), + WS_SUCCESS); +#endif + RunKexReplyHandshake(&harness, &result); + AssertTrue(result.clientSuccess); + AssertTrue(result.serverSuccess); + PumpDuplexPair(&harness); + + data = (byte*)WMALLOC(MAX_IGNORE_DATA_SZ + 1, NULL, DYNTYPE_BUFFER); + AssertNotNull(data); + WMEMSET(data, 0xA5, MAX_IGNORE_DATA_SZ + 1); + + AssertIntEQ(wolfSSH_SendIgnore(harness.client, data, + MAX_IGNORE_DATA_SZ + 1), WS_BAD_ARGUMENT); + AssertIntEQ(harness.serverIo.inbound.len, 0); + + peerSeq = harness.server->peerSeq; + AssertIntEQ(wolfSSH_SendIgnore(harness.client, data, MAX_IGNORE_DATA_SZ), + WS_SUCCESS); + AssertTrue(harness.serverIo.inbound.len > MAX_IGNORE_DATA_SZ); + PumpDuplexPair(&harness); + AssertIntEQ(harness.server->peerSeq, peerSeq + 1); + AssertTrue(wolfSSH_get_error(harness.server) != WS_OVERFLOW_E); + + WFREE(data, NULL, DYNTYPE_BUFFER); + FreeKexReplyHarness(&harness); +} + /* draft-miller-sshm-strict-kex resets the sequence numbers at every * NEWKEYS, not just the initial one. Establish a session, note where the * counters have got to, then rekey: with the mitigation on they fall back @@ -14136,6 +14187,129 @@ static void TestShutdownFlushClearsWantWrite(void) wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); } + + +/* wolfSSH_SendIgnore() sends the caller's payload; a NULL payload sends 128 + * zero bytes. */ +static void TestSendIgnorePayload(void) +{ + static const byte payload[] = { 'w', 'o', 'l', 'f', 'S', 'S', 'H' }; + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + MemIo io; + byte out[512]; + word32 dataSz; + word32 i; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(ctx); + + wolfSSH_SetIORecv(ctx, MemRecv); + wolfSSH_SetIOSend(ctx, MemSend); + wolfSSH_CTX_SetStrictKex(ctx, 0); + + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE; + + MemIoInit(&io, NULL, 0, out, sizeof(out)); + wolfSSH_SetIOReadCtx(ssh, &io); + wolfSSH_SetIOWriteCtx(ssh, &io); + + AssertIntEQ(wolfSSH_SendIgnore(ssh, NULL, 1), WS_BAD_ARGUMENT); + AssertIntEQ(io.outSz, 0); + + /* A size that would wrap the packet size sums is refused before the + * payload is read; payload is far shorter than the size claimed. */ + AssertIntEQ(wolfSSH_SendIgnore(ssh, payload, 0xFFFFFFFB), + WS_BAD_ARGUMENT); + AssertIntEQ(wolfSSH_SendIgnore(ssh, payload, MAX_IGNORE_DATA_SZ + 1), + WS_BAD_ARGUMENT); + AssertIntEQ(io.outSz, 0); + AssertFalse(wolfSSH_OutputPending(ssh)); + + AssertIntEQ(wolfSSH_SendIgnore(ssh, payload, sizeof(payload)), + WS_SUCCESS); + AssertTrue(io.outSz >= 10 + sizeof(payload)); + AssertIntEQ(ParseMsgId(out, io.outSz), MSGID_IGNORE); + dataSz = ((word32)out[6] << 24) | ((word32)out[7] << 16) | + ((word32)out[8] << 8) | (word32)out[9]; + AssertIntEQ(dataSz, sizeof(payload)); + AssertIntEQ(WMEMCMP(out + 10, payload, sizeof(payload)), 0); + + MemIoInit(&io, NULL, 0, out, sizeof(out)); + AssertIntEQ(wolfSSH_SendIgnore(ssh, NULL, 0), WS_SUCCESS); + AssertTrue(io.outSz >= 10 + 128); + AssertIntEQ(ParseMsgId(out, io.outSz), MSGID_IGNORE); + dataSz = ((word32)out[6] << 24) | ((word32)out[7] << 16) | + ((word32)out[8] << 8) | (word32)out[9]; + AssertIntEQ(dataSz, 128); + for (i = 0; i < dataSz; i++) { + AssertIntEQ(out[10 + i], 0); + } + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} + +/* The largest IGNORE wolfSSH_SendIgnore() takes is accepted by a wolfSSH + * peer before keys are active. */ +static void TestSendIgnoreLargestPlain(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* tx; + WOLFSSH* rx; + MemIo txIo, rxIo; + byte* data; + byte* wire; + byte rxOut[64]; + /* Room for an oversized packet, so the peer is what judges it. */ + word32 wireSz = MAX_PACKET_SZ + 256; + + data = (byte*)WMALLOC(MAX_IGNORE_DATA_SZ, NULL, DYNTYPE_BUFFER); + AssertNotNull(data); + wire = (byte*)WMALLOC(wireSz, NULL, DYNTYPE_BUFFER); + AssertNotNull(wire); + WMEMSET(data, 0x5A, MAX_IGNORE_DATA_SZ); + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + AssertNotNull(ctx); + wolfSSH_SetIORecv(ctx, MemRecv); + wolfSSH_SetIOSend(ctx, MemSend); + wolfSSH_CTX_SetStrictKex(ctx, 0); + + tx = wolfSSH_new(ctx); + AssertNotNull(tx); + tx->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE; + /* A rekey's KEXINIT fits neither buffer, so keep a build-time + * DEFAULT_HIGHWATER_MARK from firing one on either end. */ + AssertIntEQ(wolfSSH_SetHighwater(tx, 0), WS_SUCCESS); + MemIoInit(&txIo, NULL, 0, wire, wireSz); + wolfSSH_SetIOReadCtx(tx, &txIo); + wolfSSH_SetIOWriteCtx(tx, &txIo); + + AssertIntEQ(wolfSSH_SendIgnore(tx, data, MAX_IGNORE_DATA_SZ), + WS_SUCCESS); + AssertTrue(txIo.outSz > MAX_IGNORE_DATA_SZ); + + rx = wolfSSH_new(ctx); + AssertNotNull(rx); + rx->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE; + AssertIntEQ(wolfSSH_SetHighwater(rx, 0), WS_SUCCESS); + MemIoInit(&rxIo, wire, txIo.outSz, rxOut, sizeof(rxOut)); + wolfSSH_SetIOReadCtx(rx, &rxIo); + wolfSSH_SetIOWriteCtx(rx, &rxIo); + + AssertIntEQ(DoReceive(rx), WS_SUCCESS); + AssertIntEQ(rxIo.inOff, rxIo.inSz); + AssertIntEQ(rx->peerSeq, 1); + + wolfSSH_free(rx); + wolfSSH_free(tx); + wolfSSH_CTX_free(ctx); + WFREE(wire, NULL, DYNTYPE_BUFFER); + WFREE(data, NULL, DYNTYPE_BUFFER); +} #endif /* !NO_WOLFSSH_CLIENT */ @@ -22345,6 +22519,8 @@ int main(int argc, char** argv) TestPeerDisconnectKeepsTrafficQueued(1); TestShutdownFlushesWithNoChannel(); TestShutdownFlushClearsWantWrite(); + TestSendIgnorePayload(); + TestSendIgnoreLargestPlain(); #endif #ifndef NO_WOLFSSH_SERVER TestShutdownFlushesQueuedUserAuthFailure(); @@ -22445,6 +22621,7 @@ int main(int argc, char** argv) TestOutOfTurnKexMsgRejectedWithStrictKex(); TestStrictKexFailedNewKeysNotCounted(); TestStrictKexHoldsSendIgnore(); + TestSendIgnoreLargestKeyed(); #ifndef WOLFSSH_NO_AES_GCM TestPreKexInitInjectionAcceptedWithoutStrictKex(); #endif diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 223c661aa..63931b0b4 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -873,6 +873,12 @@ WOLFSSH_LOCAL int CheckAlgoList(const char* list, byte type); * a tunable, so deliberately not overridable. */ #define MAX_CHANNEL_PACKET_SZ (MAX_PACKET_SZ - CHANNEL_PACKET_OVERHEAD_SZ) +/* Largest SSH_MSG_IGNORE data that a peer's MAX_PACKET_SZ check accepts: + * the same framing, worst-case padding and MAC, with a string header. */ +#define MAX_IGNORE_DATA_SZ \ + (MAX_PACKET_SZ - (LENGTH_SZ + PAD_LENGTH_SZ + MSG_ID_SZ + LENGTH_SZ \ + + (AES_BLOCK_SIZE + MIN_PAD_LENGTH - 1) + MAX_HMAC_SZ)) + /* Both sizes are overridable, and CtxInit() takes DEFAULT_MAX_PACKET_SZ * unchecked, so assert the default frames too. */ #if DEFAULT_MAX_PACKET_SZ > (MAX_PACKET_SZ - CHANNEL_PACKET_OVERHEAD_MAX) @@ -1101,11 +1107,6 @@ typedef struct HandshakeInfo { } privKey; } HandshakeInfo; -#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP) -WOLFSSH_LOCAL int wolfSSH_GetPath(const char* defaultPath, byte* in, - word32 inSz, char* out, word32* outSz); -#endif - #ifdef WOLFSSH_SFTP #define WOLFSSH_MAX_SFTPOFST 3 diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 750022989..bc819bd7d 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -250,6 +250,9 @@ typedef enum WS_Text { * available. * * Thus, a return value of strSz or more means that the output was truncated. + * This holds on every build. Unless ssh or str is NULL or strSz is 0, str is + * always terminated; an unknown id, or a value not yet negotiated, gives an + * empty string and returns 0. */ WOLFSSH_API size_t wolfSSH_GetText(WOLFSSH *ssh, WS_Text id, char *str, @@ -1002,8 +1005,12 @@ WOLFSSH_API int wolfSSH_extended_data_read(WOLFSSH* ssh, byte* out, /* Starts a key exchange. A clean start leaves ssh->error alone; only a * failure records its code there. */ WOLFSSH_API int wolfSSH_TriggerKeyExchange(WOLFSSH* ssh); -/* Returns WS_INVALID_STATE_E before the initial KEX completes when strict - * KEX is offered. */ +/* Sends an SSH_MSG_IGNORE carrying buf. A NULL buf with a zero bufSz sends + * 128 zero bytes instead. A bufSz whose packet, with worst-case padding and + * MAC, would exceed MAX_PACKET_SZ is WS_BAD_ARGUMENT; the largest allowed is + * 34907 with the default MAX_PACKET_SZ and a 64-byte maximum digest. Returns + * WS_INVALID_STATE_E before the initial KEX completes when strict KEX is + * offered. */ WOLFSSH_API int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz); /* One disconnect ends the session, so a second call reports WS_DISCONNECT. * The exception is a disconnect of this side's own, left short by a diff --git a/wolfssh/wolfsftp.h b/wolfssh/wolfsftp.h index 29b9bc84f..329f5a17c 100644 --- a/wolfssh/wolfsftp.h +++ b/wolfssh/wolfsftp.h @@ -293,6 +293,8 @@ WOLFSSH_API void wolfSSH_SFTP_Interrupt(WOLFSSH* ssh); /* functions used for commands */ WOLFSSH_API int wolfSSH_SFTP_Remove(WOLFSSH* ssh, char* f); +/* Sends atr with the request; the server picks the mode when atr carries no + * permissions. A NULL atr requests permissions 0777. */ WOLFSSH_API int wolfSSH_SFTP_MKDIR(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr); WOLFSSH_API int wolfSSH_SFTP_RMDIR(WOLFSSH* ssh, char* dir);