diff --git a/src/internal.c b/src/internal.c index 5f0407c22..fcb65618a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -4098,7 +4098,31 @@ int CheckAlgoList(const char* list, byte type) } else { byte tokenType = TYPE_OTHER; - byte id = NameToIdType(name, nameSz, &tokenType); + byte id; + word32 j; + word32 atCount = 0; + + /* Skipped names still go out in KEXINIT. RFC 4251 Section 6 + * and RFC 4250 Section 4.6: at most 64 bytes, printable + * US-ASCII with no space, control code or DEL, at most one + * '@', with text on both sides of it. */ + if (nameSz > WOLFSSH_MAX_ALGO_NAME_SZ) { + ret = WS_INVALID_ALGO_ID; + break; + } + for (j = 0; j < nameSz; j++) { + if ((byte)name[j] <= 0x20 || (byte)name[j] >= 0x7F) + break; + if (name[j] == '@' && (++atCount > 1 || j == 0 + || j == nameSz - 1)) + break; + } + if (j < nameSz) { + ret = WS_INVALID_ALGO_ID; + break; + } + + id = NameToIdType(name, nameSz, &tokenType); if (id == ID_NONE) { /* "none" names the null cipher/MAC, not a host key. It diff --git a/tests/api.c b/tests/api.c index 0de26d9d4..b83bdaf6a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -8692,6 +8692,40 @@ static void test_wolfSSH_CheckAlgoList(void) WSNPRINTF(listBuf, sizeof(listBuf), "%s,bogus", validCipher); AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS); + /* A skipped name still goes out in KEXINIT, so it must be well formed. */ + WSNPRINTF(listBuf, sizeof(listBuf), "%s,%064d", validCipher, 0); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,%065d", validCipher, 0); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bo gus", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bo\tgus", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bo\x7fgus", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bo\xc3\xa9gus", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bogus@example.com", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bo@gus@example.com", + validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,@example.com", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,bogus@", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + WSNPRINTF(listBuf, sizeof(listBuf), "%s,@", validCipher); + AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), + WS_INVALID_ALGO_ID); + /* A known name in the wrong category still fails the whole list. */ WSNPRINTF(listBuf, sizeof(listBuf), "%s,%s", validCipher, aKexName); AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), diff --git a/wolfssh/internal.h b/wolfssh/internal.h index cd32096c2..368df319d 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -701,6 +701,8 @@ enum NameIdType { /* Per-field cap on the number of names in a name-list. */ #define WOLFSSH_MAX_NAMELIST_CNT 64 #endif +/* RFC 4251 Section 6 byte cap on one algorithm name. */ +#define WOLFSSH_MAX_ALGO_NAME_SZ 64 #ifndef WOLFSSH_DEFAULT_GEXDH_MIN #define WOLFSSH_DEFAULT_GEXDH_MIN 2048 #endif diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 57a4e253e..3987428ea 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -175,6 +175,9 @@ WOLFSSH_API int wolfSSH_ReadCert_file(const char* name, /* SetAlgoList* validate the list, returning WS_SUCCESS, WS_INVALID_ALGO_ID for * a bad list, or WS_SSH_CTX_NULL_E / WS_SSH_NULL_E for a NULL ctx / ssh. + * Unknown names are skipped, but every name must be 1 to 64 printable + * US-ASCII bytes with no space and at most one '@', not first or last + * (RFC 4251 Section 6, RFC 4250 Section 4.6). * Kex/Cipher/Mac reject NULL. Key accepts NULL only on a server, restoring the * default of deriving the host key list from the loaded private keys; a client * has no such fallback. KeyAccepted accepts NULL on either side, but that