/* unit.c * * Copyright (C) 2014-2026 wolfSSL Inc. * * This file is part of wolfSSH. * * wolfSSH is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * wolfSSH is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with wolfSSH. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #ifdef WOLFSSL_USER_SETTINGS #include #else #include #endif #include #include #include #include #include #include #include #include #include #ifndef WOLFSSH_NO_RSA #include #include #endif #define WOLFSSH_TEST_HEX2BIN #include #include "unit.h" /* Regression coverage for non-CA intermediate promotion. * Needs WOLFSSH_TEST_INTERNAL (the test bodies are in that section), the cert * manager, runtime cert generation to forge the attack cert, ECDSA (the test * certs are ECC), and a filesystem to load the test certs. */ #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_CERTS) && \ defined(WOLFSSL_CERT_GEN) && !defined(WOLFSSH_NO_ECDSA) && \ !defined(NO_FILESYSTEM) #define WOLFSSH_TEST_CERTMAN_PROMOTE /* The certman helpers use malloc/free and LONG_MAX; pull these in here so * the tests build even when the SCP block below is not compiled. */ #include #include #include #include #include #endif #ifdef WOLFSSH_SFTP #include #endif #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \ !defined(NO_FILESYSTEM) && !defined(WOLFSSL_NUCLEUS) && \ !defined(_WIN32) && !defined(WOLFSSH_ZEPHYR) #include #include #include #include #include #include #endif #ifdef WOLFSSH_TEST_INTERNAL typedef struct { const char* name; const char* proto; int ioError; int expected; int side; /* WOLFSSH_ENDPOINT_CLIENT or WOLFSSH_ENDPOINT_SERVER */ } ProtoIdTestVector; typedef struct { const ProtoIdTestVector* tv; word32 offset; } ProtoIdTestState; static const ProtoIdTestVector protoIdTestVectors[] = { /* Pre-version banner lines (RFC 4253 Section 4.2). DoProtoId on the * client skips informational lines before the version string. */ { "banner lines LF before version", "this is a test\n" "more test line\n" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, { "banner lines CRLF before version", "this is a test\r\n" "more test line\r\n" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, /* Valid version strings with no banner. */ { "version with comment CRLF", "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING " some comment\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, { "version CRLF no comment", "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, { "version LF only", "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, /* Case rejection. DoProtoId uses WSTRNCMP. */ { "lowercase ssh prefix", "ssh-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "mixed case SSH prefix", "Ssh-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, /* Case rejection. DoProtoId uses WSTRNCMP. */ { "lowercase ssh prefix", "ssh-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_SERVER }, { "mixed case SSH prefix", "Ssh-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_SERVER }, /* OpenSSH peer identification. */ { "OpenSSH version string", "SSH-2.0-OpenSSH_8.9\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, /* Wrong SSH versions. */ { "SSH-1.99 version", "SSH-1.99-server\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, { "SSH-1.0 version", "SSH-1.0-old\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, { "SSH-3.0 future version", "SSH-3.0-future\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, /* Malformed or missing version strings. Cases where the peer sends * incomplete data and then closes the connection map to * WS_SOCKET_ERROR_E because GetInputLine treats a 0-byte recv as EOF. */ { "no newline terminator", "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING, 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "empty line only", "\r\n", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "bare newline", "\n", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "not SSH at all", "HTTP/1.1 200 OK\r\n", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "SSH- prefix but truncated", "SSH-\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, /* Line longer than the 255-byte per-line cap (RFC 4253 4.2). */ { "overlong line before version", "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, /* Banner line of exactly 255 bytes including CRLF (253 chars + CRLF). * The cap is inclusive so this line should be accepted. */ { "banner exactly 255 bytes", "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "012\r\n" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT }, /* Banner consumed, then peer closes mid-version-line. Exercises a * second GetInputLine call returning WS_SOCKET_ERROR_E after a prior * call succeeded. */ { "banner then truncated SSH line", "banner line\nSSH-2.0-", 0, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, /* More than WOLFSSH_MAX_BANNER_LINES (default 10) banner lines before * the version string. The 11th banner line trips the cap. */ { "too many banner lines", "b1\nb2\nb3\nb4\nb5\nb6\nb7\nb8\nb9\nb10\nb11\n" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_CLIENT }, /* IO error cases. The IO callback returns WS_CBIO_ERR_* values. * ReceiveData maps WS_CBIO_ERR_GENERAL to -1, and GetInputLine * returns WS_SOCKET_ERROR_E for that. WS_CBIO_ERR_WANT_READ is * mapped to WS_WANT_READ by ReceiveData and GetInputLine. */ { "IO recv general error", "", WS_CBIO_ERR_GENERAL, WS_SOCKET_ERROR_E, WOLFSSH_ENDPOINT_CLIENT }, { "IO recv want read", "", WS_CBIO_ERR_WANT_READ, WS_WANT_READ, WOLFSSH_ENDPOINT_CLIENT }, /* Server-side: a wolfSSH server reading the client's identification * string MUST reject any non-"SSH-" line. The client is required to * send the version string first per RFC 4253 4.2. */ { "server accepts SSH- line", "SSH-2.0-OpenSSH_8.9\r\n", 0, WS_SUCCESS, WOLFSSH_ENDPOINT_SERVER }, { "server rejects banner before SSH-", "client banner\r\n" "SSH-2.0-OpenSSH_8.9\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_SERVER }, { "server rejects non-SSH protocol", "GET / HTTP/1.1\r\n", 0, WS_VERSION_E, WOLFSSH_ENDPOINT_SERVER }, }; static int RecvFromPtr(WOLFSSH* ssh, void* data, word32 sz, void* ctx) { ProtoIdTestState* state; const ProtoIdTestVector* tv; word32 protoSz; word32 remaining; WOLFSSH_UNUSED(ssh); state = (ProtoIdTestState*)ctx; tv = state->tv; if (tv->ioError) return tv->ioError; protoSz = (word32)WSTRLEN(tv->proto); if (state->offset >= protoSz) { /* Simulate the peer closing the connection after sending all the * data in the test vector. */ return 0; } remaining = protoSz - state->offset; if (remaining < sz) sz = remaining; WMEMCPY(data, tv->proto + state->offset, sz); state->offset += sz; return sz; } /* Scripted IO mock for testing non-blocking control flow. A script is a * sequence of steps. Each step either delivers a chunk of bytes or signals * a single WS_CBIO_ERR_WANT_READ to the caller. After the script is * exhausted the mock returns 0 (clean EOF). Long byte chunks are delivered * across multiple IO callback invocations as needed. */ typedef struct { const char* bytes; /* NULL means "return WS_CBIO_ERR_WANT_READ" */ word32 sz; /* 0 means strlen(bytes) */ } ProtoIdScriptStep; typedef struct { const ProtoIdScriptStep* steps; word32 stepCount; word32 stepIdx; word32 stepOffset; } ProtoIdScriptState; typedef struct { const char* name; const ProtoIdScriptStep* steps; word32 stepCount; int side; int expected; } ProtoIdScriptVector; static int RecvFromScript(WOLFSSH* ssh, void* data, word32 sz, void* ctx) { ProtoIdScriptState* s; const ProtoIdScriptStep* step; word32 stepSz; word32 remaining; WOLFSSH_UNUSED(ssh); s = (ProtoIdScriptState*)ctx; while (s->stepIdx < s->stepCount) { step = &s->steps[s->stepIdx]; if (step->bytes == NULL) { /* WANT_READ marker; consume the step exactly once. */ s->stepIdx++; s->stepOffset = 0; return WS_CBIO_ERR_WANT_READ; } stepSz = step->sz ? step->sz : (word32)WSTRLEN(step->bytes); if (s->stepOffset >= stepSz) { s->stepIdx++; s->stepOffset = 0; continue; } remaining = stepSz - s->stepOffset; if (remaining < sz) sz = remaining; WMEMCPY(data, step->bytes + s->stepOffset, sz); s->stepOffset += sz; if (s->stepOffset == stepSz) { s->stepIdx++; s->stepOffset = 0; } return (int)sz; } /* Script exhausted: simulate peer closing the connection. */ return 0; } /* WANT_READ delivered partway through a banner line. Driver retries and * the partial line is completed cleanly. */ static const ProtoIdScriptStep wantReadMidBannerSteps[] = { { "this is a part", 0 }, { NULL, 0 }, { "ial banner\n" "SSH-2.0-wolfSSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0 }, }; /* WANT_READ delivered partway through the SSH version line itself. */ static const ProtoIdScriptStep wantReadMidVersionSteps[] = { { "SSH-2.0-wolf", 0 }, { NULL, 0 }, { "SSHv" LIBWOLFSSH_VERSION_STRING "\r\n", 0 }, }; /* Eleven banner lines, each followed by WS_WANT_READ. WOLFSSH_MAX_BANNER_LINES * defaults to 10, so the eleventh line must trip the cap. This is a * regression guard: an earlier version kept the banner counter as a * DoProtoId stack local, which got reset on every WS_WANT_READ retry and * effectively bypassed the cap. */ static const ProtoIdScriptStep manyBannersWantReadSteps[] = { { "b1\n", 0 }, { NULL, 0 }, { "b2\n", 0 }, { NULL, 0 }, { "b3\n", 0 }, { NULL, 0 }, { "b4\n", 0 }, { NULL, 0 }, { "b5\n", 0 }, { NULL, 0 }, { "b6\n", 0 }, { NULL, 0 }, { "b7\n", 0 }, { NULL, 0 }, { "b8\n", 0 }, { NULL, 0 }, { "b9\n", 0 }, { NULL, 0 }, { "b10\n", 0 }, { NULL, 0 }, { "b11\n", 0 }, }; #define SCRIPT_LEN(s) (word32)(sizeof(s)/sizeof((s)[0])) static const ProtoIdScriptVector protoIdScriptVectors[] = { { "WANT_READ mid-banner resumes", wantReadMidBannerSteps, SCRIPT_LEN(wantReadMidBannerSteps), WOLFSSH_ENDPOINT_CLIENT, WS_SUCCESS }, { "WANT_READ mid-SSH-version resumes", wantReadMidVersionSteps, SCRIPT_LEN(wantReadMidVersionSteps), WOLFSSH_ENDPOINT_CLIENT, WS_SUCCESS }, { "banners across WANT_READ still trip cap", manyBannersWantReadSteps, SCRIPT_LEN(manyBannersWantReadSteps), WOLFSSH_ENDPOINT_CLIENT, WS_VERSION_E }, }; /* DoProtoId() Unit Test */ static int test_DoProtoId(void) { WOLFSSH_CTX* clientCtx; WOLFSSH_CTX* serverCtx; WOLFSSH_CTX* ctx; WOLFSSH* ssh; const ProtoIdTestVector* tv; int tc = (int)(sizeof(protoIdTestVectors)/sizeof(protoIdTestVectors[0])); int i; int ret; int failures = 0; clientCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (clientCtx == NULL) return -100; serverCtx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (serverCtx == NULL) { wolfSSH_CTX_free(clientCtx); return -101; } wolfSSH_SetIORecv(clientCtx, RecvFromPtr); wolfSSH_SetIORecv(serverCtx, RecvFromPtr); for (i = 0, tv = protoIdTestVectors; i < tc; i++, tv++) { ProtoIdTestState state; state.tv = tv; state.offset = 0; ctx = (tv->side == WOLFSSH_ENDPOINT_SERVER) ? serverCtx : clientCtx; ssh = wolfSSH_new(ctx); if (ssh == NULL) { fprintf(stderr, "\t[%d] \"%s\" FAIL: wolfSSH_new returned NULL\n", i, tv->name); failures++; continue; } wolfSSH_SetIOReadCtx(ssh, &state); ret = wolfSSH_TestDoProtoId(ssh); if (ret != tv->expected) { fprintf(stderr, "\t[%d] \"%s\" FAIL: got %d, expected %d\n", i, tv->name, ret, tv->expected); failures++; } wolfSSH_free(ssh); } /* Scripted-IO vectors: exercise non-blocking flow where ReceiveData * returns WS_WANT_READ partway through a line. The driver re-enters * DoProtoId until it returns something other than WS_WANT_READ. */ wolfSSH_SetIORecv(clientCtx, RecvFromScript); wolfSSH_SetIORecv(serverCtx, RecvFromScript); { const ProtoIdScriptVector* sv; int sc = (int)(sizeof(protoIdScriptVectors) / sizeof(protoIdScriptVectors[0])); for (i = 0, sv = protoIdScriptVectors; i < sc; i++, sv++) { ProtoIdScriptState scriptState; scriptState.steps = sv->steps; scriptState.stepCount = sv->stepCount; scriptState.stepIdx = 0; scriptState.stepOffset = 0; ctx = (sv->side == WOLFSSH_ENDPOINT_SERVER) ? serverCtx : clientCtx; ssh = wolfSSH_new(ctx); if (ssh == NULL) { fprintf(stderr, "\t[script %d] \"%s\" FAIL: wolfSSH_new returned NULL\n", i, sv->name); failures++; continue; } wolfSSH_SetIOReadCtx(ssh, &scriptState); do { ret = wolfSSH_TestDoProtoId(ssh); } while (ret == WS_WANT_READ); if (ret != sv->expected) { fprintf(stderr, "\t[script %d] \"%s\" FAIL: got %d, expected %d\n", i, sv->name, ret, sv->expected); failures++; } wolfSSH_free(ssh); } } wolfSSH_CTX_free(serverCtx); wolfSSH_CTX_free(clientCtx); return failures; } /* GetMpint() Unit Test */ typedef struct { const char* name; const byte* input; word32 inputSz; int expected; /* expected return value */ word32 expectedSz; /* expected mpint size on success */ } GetMpintTestVector; static const byte mpintPositive[] = { 0, 0, 0, 4, 0x12, 0x34, 0x56, 0x78 }; static const byte mpintLeadZero[] = { 0, 0, 0, 3, 0x00, 0x80, 0x01 }; static const byte mpintZeroLen[] = { 0, 0, 0, 0 }; static const byte mpintNegative[] = { 0, 0, 0, 2, 0x80, 0x01 }; static const byte mpintNegOne[] = { 0, 0, 0, 1, 0xFF }; static const byte mpintShort[] = { 0, 0, 0, 5, 0x01, 0x02 }; static const GetMpintTestVector getMpintTestVectors[] = { { "positive", mpintPositive, sizeof(mpintPositive), WS_SUCCESS, 4 }, { "canonical leading zero", mpintLeadZero, sizeof(mpintLeadZero), WS_SUCCESS, 3 }, { "zero length", mpintZeroLen, sizeof(mpintZeroLen), WS_SUCCESS, 0 }, { "negative", mpintNegative, sizeof(mpintNegative), WS_PARSE_E, 0 }, { "negative one", mpintNegOne, sizeof(mpintNegOne), WS_PARSE_E, 0 }, { "length past end of buffer", mpintShort, sizeof(mpintShort), WS_BUFFER_E, 0 }, }; static int test_GetMpint(void) { const GetMpintTestVector* tv; int tc = (int)(sizeof(getMpintTestVectors)/sizeof(getMpintTestVectors[0])); int i; int ret; int failures = 0; for (i = 0, tv = getMpintTestVectors; i < tc; i++, tv++) { const byte* mpint = NULL; word32 mpintSz = 0; word32 idx = 0; ret = GetMpint(&mpintSz, &mpint, tv->input, tv->inputSz, &idx); if (ret != tv->expected) { fprintf(stderr, "\t[%d] \"%s\" FAIL: got %d, expected %d\n", i, tv->name, ret, tv->expected); failures++; continue; } if (ret == WS_SUCCESS) { const byte* expectedPtr = (tv->expectedSz > 0) ? tv->input + LENGTH_SZ : NULL; if (mpintSz != tv->expectedSz) { fprintf(stderr, "\t[%d] \"%s\" FAIL: size %u, expected %u\n", i, tv->name, mpintSz, tv->expectedSz); failures++; } else if (mpint != expectedPtr || idx != LENGTH_SZ + mpintSz) { fprintf(stderr, "\t[%d] \"%s\" FAIL: bad pointer or index\n", i, tv->name); failures++; } } } return failures; } #endif /* WOLFSSH_TEST_INTERNAL */ /* Key Derivation Function (KDF) Unit Test */ typedef struct { byte hashId; byte keyId; const char* k; const char* h; const char* sessionId; const char* expectedKey; } KdfTestVector; #ifndef NO_SHA /** Test Vector Set #1: SHA-1 **/ const char kdfTvSet1k[] = "35618FD3AABF980A5F766408961600D4933C60DD7B22D69EEB4D7A987C938F6F" "7BB2E60E0F638BB4289297B588E6109057325F010D021DF60EBF8BE67AD9C3E2" "6376A326A16210C7AF07B3FE562B8DD1DCBECB17AA7BFAF38708B0136120B2FC" "723E93EF4237AC3737BAE3A16EC03F605C7EEABFD526B38C826B506BBAECD2F7" "9932F1371AEABFBEB4F8222313506677330C714A2A6FDC70CB859B581AA18625" "ECCB6BA9DDEEAECF0E41D9E5076B899B477112E59DDADC4B4D9C13E9F07E1107" "B560FEFDC146B8ED3E73441D05345031C35F9E6911B00319481D80015855BE4D" "1C7D7ACC8579B1CC2E5F714109C0882C3B57529ABDA1F2255D2B27C4A83AE11E"; const char kdfTvSet1h[] = "40555741F6DE70CDC4E740104A97E75473F49064"; const char kdfTvSet1sid[] = "40555741F6DE70CDC4E740104A97E75473F49064"; const char kdfTvSet1a[] = "B2EC4CF6943632C39972EE2801DC7393"; const char kdfTvSet1b[] = "BC92238B6FA69ECC10B2B013C2FC9785"; const char kdfTvSet1c[] = "9EF0E2053F66C56F3E4503DA1C2FBD6B"; const char kdfTvSet1d[] = "47C8395B08277020A0645DA3959FA1A9"; const char kdfTvSet1e[] = "EE436BFDABF9B0313224EC800E7390445E2F575E"; const char kdfTvSet1f[] = "FB9FDEEC78B0FB258F1A4F47F6BCE166680994BB"; /** Test Vector Set #2: SHA-1 **/ const char kdfTvSet2k[] = "19FA2B7C7F4FE7DE61CDE17468C792CCEAB0E3F2CE37CDE2DAA0974BCDFFEDD4" "A29415CDB330FA6A97ECA742359DC1223B581D8AC61B43CFFDF66D20952840B0" "2593B48354E352E2A396BDF7F1C9D414FD31C2BF47E6EED306069C4F4F5F66C3" "003A90E85412A1FBE89CDFB457CDA0D832E8DA701627366ADEC95B70E8A8B7BF" "3F85775CCF36E40631B83B32CF643088F01A82C97C5C3A820EB4149F551CAF8C" "C98EE6B3065E6152FF877823F7C618C1CD93CE26DB9FAAFED222F1C93E8F4068" "BFDA4480432E14F98FFC821F05647693040B07D71DC273121D53866294434D46" "0E95CFA4AB4414705BF1F8224655F907A418A6A893F2A71019225869CB7FE988"; const char kdfTvSet2h[] = "DFB748905CC8647684C3E0B7F26A3E8E7414AC51"; const char kdfTvSet2sid[] = "DFB748905CC8647684C3E0B7F26A3E8E7414AC51"; const char kdfTvSet2a[] = "52EDBFD5E414A3CC6C7F7A0F4EA60503"; const char kdfTvSet2b[] = "926C6987696C5FFCC6511BFE34557878"; const char kdfTvSet2c[] = "CB6D56EC5B9AFECD326D544DA2D22DED"; const char kdfTvSet2d[] = "F712F6451F1BD6CE9BAA597AC87C5A24"; const char kdfTvSet2e[] = "E42FC62C76B76B37818F78292D3C2226D0264760"; const char kdfTvSet2f[] = "D14BE4DD0093A3E759580233C80BB8399CE4C4E7"; #endif /** Test Vector Set #3: SHA-256 **/ const char kdfTvSet3k[] = "6AC382EAACA093E125E25C24BEBC84640C11987507344B5C739CEB84A9E0B222" "B9A8B51C839E5EBE49CFADBFB39599764ED522099DC912751950DC7DC97FBDC0" "6328B68F22781FD315AF568009A5509E5B87A11BF527C056DAFFD82AB6CBC25C" "CA37143459E7BC63BCDE52757ADEB7DF01CF12173F1FEF8102EC5AB142C213DD" "9D30696278A8D8BC32DDE9592D28C078C6D92B947D825ACAAB6494846A49DE24" "B9623F4889E8ADC38E8C669EFFEF176040AD945E90A7D3EEC15EFEEE78AE7104" "3C96511103A16BA7CAF0ACD0642EFDBE809934FAA1A5F1BD11043649B25CCD1F" "EE2E38815D4D5F5FC6B4102969F21C22AE1B0E7D3603A556A13262FF628DE222"; const char kdfTvSet3h[] = "7B7001185E256D4493445F39A55FB905E6321F4B5DD8BBF3100D51BA0BDA3D2D"; const char kdfTvSet3sid[] = "7B7001185E256D4493445F39A55FB905E6321F4B5DD8BBF3100D51BA0BDA3D2D"; const char kdfTvSet3a[] = "81F0330EF6F05361B3823BFDED6E1DE9"; const char kdfTvSet3b[] = "3F6FD2065EEB2B0B1D93195A1FED48A5"; const char kdfTvSet3c[] = "C35471034E6FD6547613178E23435F21"; const char kdfTvSet3d[] = "7E9D79032090D99F98B015634DD9F462"; const char kdfTvSet3e[] = "24EE559AD7CE712B685D0B2271E443C17AB1D1DCEB5A360569D25D5DC243002F"; const char kdfTvSet3f[] = "C3419C2B966235869D714BA5AC48DDB7D9E35C8C19AAC73422337A373453607E"; /** Test Vector Set #4: SHA-256 **/ const char kdfTvSet4k[] = "44708C76616F700BD31B0C155EF74E36390EEB39BC5C32CDC90E21922B0ED930" "B5B519C8AFEBEF0F4E4FB5B41B81D649D2127506620B594E9899F7F0D442ECDD" "D68308307B82F00065E9D75220A5A6F5641795772132215A236064EA965C6493" "C21F89879730EBBC3C20A22D8F5BFD07B525B194323B22D8A49944D1AA58502E" "756101EF1E8A91C9310E71F6DB65A3AD0A542CFA751F83721A99E89F1DBE5497" "1A3620ECFFC967AA55EED1A42D6E7A138B853557AC84689889F6D0C8553575FB" "89B4E13EAB5537DA72EF16F0D72F5E8505D97F110745193D550FA315FE88F672" "DB90D73843E97BA1F3D087BA8EB39025BBFFAD37589A6199227303D9D8E7F1E3"; const char kdfTvSet4h[] = "FE3727FD99A5AC7987C2CFBE062129E3027BF5E10310C6BCCDE9C916C8329DC2"; const char kdfTvSet4sid[] = "FFFA598BC0AD2AE84DC8DC05B1F72C5B0134025AE7EDF8A2E8DB11472E18E1FC"; const char kdfTvSet4a[] = "36730BAE8DE5CB98898D6B4A00B37058"; const char kdfTvSet4b[] = "5DFE446A83F40E8358D28CB97DF8F340"; const char kdfTvSet4c[] = "495B7AFED0872B761437728E9E94E2B8"; const char kdfTvSet4d[] = "C1474B3925BEC36F0B7F6CC698E949C8"; const char kdfTvSet4e[] = "B730F8DF6A0697645BE261169486C32A11612229276CBAC5D8B3669AFB2E4262"; const char kdfTvSet4f[] = "14A5EA98245FB058978B82A3CB092B1CCA7CE0109A4F98C16E1529579D58B819"; #define HASH_SHA WC_HASH_TYPE_SHA #define HASH_SHA256 WC_HASH_TYPE_SHA256 static const KdfTestVector kdfTestVectors[] = { #ifndef NO_SHA {HASH_SHA, 'A', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1a}, {HASH_SHA, 'B', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1b}, {HASH_SHA, 'C', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1c}, {HASH_SHA, 'D', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1d}, {HASH_SHA, 'E', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1e}, {HASH_SHA, 'F', kdfTvSet1k, kdfTvSet1h, kdfTvSet1sid, kdfTvSet1f}, {HASH_SHA, 'A', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2a}, {HASH_SHA, 'B', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2b}, {HASH_SHA, 'C', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2c}, {HASH_SHA, 'D', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2d}, {HASH_SHA, 'E', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2e}, {HASH_SHA, 'F', kdfTvSet2k, kdfTvSet2h, kdfTvSet2sid, kdfTvSet2f}, #endif {HASH_SHA256, 'A', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3a}, {HASH_SHA256, 'B', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3b}, {HASH_SHA256, 'C', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3c}, {HASH_SHA256, 'D', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3d}, {HASH_SHA256, 'E', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3e}, {HASH_SHA256, 'F', kdfTvSet3k, kdfTvSet3h, kdfTvSet3sid, kdfTvSet3f}, {HASH_SHA256, 'A', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4a}, {HASH_SHA256, 'B', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4b}, {HASH_SHA256, 'C', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4c}, {HASH_SHA256, 'D', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4d}, {HASH_SHA256, 'E', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4e}, {HASH_SHA256, 'F', kdfTvSet4k, kdfTvSet4h, kdfTvSet4sid, kdfTvSet4f} }; static int test_KDF(void) { int result = 0; word32 i; word32 tc = sizeof(kdfTestVectors)/sizeof(KdfTestVector); const KdfTestVector* tv = NULL; byte* k = NULL; byte* h = NULL; byte* sId = NULL; byte* eKey = NULL; word32 kSz = 0, hSz = 0, sIdSz = 0, eKeySz = 0; byte cKey[32]; /* Greater of SHA256_DIGEST_SIZE and AES_BLOCK_SIZE */ /* sId - Session ID, eKey - Expected Key, cKey - Calculated Key */ for (i = 0, tv = kdfTestVectors; i < tc; i++, tv++) { result = ConvertHexToBin(tv->k, &k, &kSz, tv->h, &h, &hSz, tv->sessionId, &sId, &sIdSz, tv->expectedKey, &eKey, &eKeySz); if (result != 0 || eKey == NULL) { printf("KDF: Could not convert test vector %u.\n", i); result = -100; } if (result == 0) { result = wolfSSH_KDF(tv->hashId, tv->keyId, cKey, eKeySz, k, kSz, h, hSz, sId, sIdSz); if (result != 0) { printf("KDF: Could not derive key.\n"); result = -101; } } if (result == 0) { if (memcmp(cKey, eKey, eKeySz) != 0) { printf("KDF: Calculated Key does not match Expected Key.\n"); result = -102; } } FreeBins(k, h, sId, eKey); k = NULL; h = NULL; sId = NULL; eKey = NULL; if (result != 0) break; } return result; } /* Key Generation Unit Test */ #ifdef WOLFSSH_KEYGEN #ifndef WOLFSSH_NO_RSA static int test_RsaKeyGen(void) { int result = 0; byte der[1200]; int derSz; derSz = wolfSSH_MakeRsaKey(der, sizeof(der), WOLFSSH_RSAKEY_DEFAULT_SZ, WOLFSSH_RSAKEY_DEFAULT_E); if (derSz < 0) { printf("RsaKeyGen: MakeRsaKey failed\n"); result = -103; } return result; } #endif #ifndef WOLFSSH_NO_ECDSA static int test_EcdsaKeyGen(void) { int result = 0; byte der[1200]; int derSz; derSz = wolfSSH_MakeEcdsaKey(der, sizeof(der), WOLFSSH_ECDSAKEY_PRIME256); if (derSz < 0) { printf("EcdsaKeyGen: MakeEcdsaKey failed\n"); result = -104; } return result; } #endif #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; byte der[1200]; int derSz; derSz = wolfSSH_MakeEd25519Key(der, sizeof(der), WOLFSSH_ED25519KEY); if (derSz < 0) { printf("Ed25519KeyGen: MakeEd25519Key failed\n"); result = -105; } return result; } #endif #ifndef WOLFSSH_NO_MLDSA static int test_MlDsaKeyGen(void) { static const struct { word32 level; word32 derSz; const char* name; } params[] = { #ifndef WOLFSSH_NO_MLDSA44 { WOLFSSH_MLDSAKEY_44, WC_MLDSA_44_BOTH_KEY_DER_SIZE, "44" }, #endif #ifndef WOLFSSH_NO_MLDSA65 { WOLFSSH_MLDSAKEY_65, WC_MLDSA_65_BOTH_KEY_DER_SIZE, "65" }, #endif #ifndef WOLFSSH_NO_MLDSA87 { WOLFSSH_MLDSAKEY_87, WC_MLDSA_87_BOTH_KEY_DER_SIZE, "87" }, #endif }; word32 i; int result = 0; for (i = 0; i < (word32)(sizeof(params) / sizeof(params[0])); i++) { byte* der; int sz; der = (byte*)WMALLOC(params[i].derSz, NULL, DYNTYPE_BUFFER); if (der == NULL) { printf("MlDsaKeyGen: alloc failed for level %s\n", params[i].name); result = -106; break; } sz = wolfSSH_MakeMlDsaKey(der, params[i].derSz, params[i].level); if (sz < 0) { printf("MlDsaKeyGen: MakeMlDsaKey level %s failed (%d)\n", params[i].name, sz); WFREE(der, NULL, DYNTYPE_BUFFER); result = -106; break; } sz = wolfSSH_MakeMlDsaKey(der, params[i].derSz - 1, params[i].level); if (sz != WS_CRYPTO_FAILED) { printf("MlDsaKeyGen: undersized buffer wrong result %d, level %s\n", sz, params[i].name); WFREE(der, NULL, DYNTYPE_BUFFER); result = -107; break; } WFREE(der, NULL, DYNTYPE_BUFFER); } if (result == 0) { int sz = wolfSSH_MakeMlDsaKey(NULL, 0, 9999); if (sz != WS_BAD_ARGUMENT) { printf("MlDsaKeyGen: invalid level wrong result %d\n", sz); result = -108; } } return result; } #endif #endif #if defined(WOLFSSH_TEST_INTERNAL) && \ (!defined(WOLFSSH_NO_HMAC_SHA1) || \ !defined(WOLFSSH_NO_HMAC_SHA1_96) || \ !defined(WOLFSSH_NO_HMAC_SHA2_256) || \ !defined(WOLFSSH_NO_HMAC_SHA2_512)) /* Minimal SSH binary packet: uint32 length, padding_length, msgId, padding. * Same layout as tests/regress.c BuildPacket (8-byte aligned body). */ static word32 BuildMacTestPacketPrefix(byte msgId, byte* out, word32 outSz) { byte padLen = 6; word32 packetLen = (word32)(1 + 1 + padLen); word32 need = UINT32_SZ + packetLen; if (outSz < need) return 0; out[0] = (byte)(packetLen >> 24); out[1] = (byte)(packetLen >> 16); out[2] = (byte)(packetLen >> 8); out[3] = (byte)(packetLen); out[4] = padLen; out[5] = msgId; WMEMSET(out + 6, 0, padLen); return need; } static int test_DoReceive_VerifyMacFailure(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int ret = WS_SUCCESS; int result = 0; byte flatSeq[LENGTH_SZ]; byte macKey[MAX_HMAC_SZ]; Hmac hmac; word32 prefixLen; word32 totalLen; byte pkt[UINT32_SZ + 8 + MAX_HMAC_SZ]; int i; struct { byte macId; int hmacType; byte macSz; byte keySz; } cases[] = { #ifndef WOLFSSH_NO_HMAC_SHA1 { ID_HMAC_SHA1, WC_SHA, WC_SHA_DIGEST_SIZE, WC_SHA_DIGEST_SIZE }, #endif #ifndef WOLFSSH_NO_HMAC_SHA1_96 { ID_HMAC_SHA1_96, WC_SHA, SHA1_96_SZ, WC_SHA_DIGEST_SIZE }, #endif #ifndef WOLFSSH_NO_HMAC_SHA2_256 { ID_HMAC_SHA2_256, WC_SHA256, WC_SHA256_DIGEST_SIZE, WC_SHA256_DIGEST_SIZE }, #endif #ifndef WOLFSSH_NO_HMAC_SHA2_512 { ID_HMAC_SHA2_512, WC_SHA512, WC_SHA512_DIGEST_SIZE, WC_SHA512_DIGEST_SIZE }, #endif }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -200; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -201; } WMEMSET(macKey, 0xA5, sizeof(macKey)); for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) { prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt)); if (prefixLen == 0) { result = -202; goto done; } totalLen = prefixLen + cases[i].macSz; ssh->peerEncryptId = ID_NONE; ssh->peerAeadMode = 0; ssh->peerBlockSz = MIN_BLOCK_SZ; ssh->peerMacId = cases[i].macId; ssh->peerMacSz = cases[i].macSz; WMEMCPY(ssh->peerKeys.macKey, macKey, cases[i].keySz); ssh->peerKeys.macKeySz = cases[i].keySz; ssh->peerSeq = 0; ssh->curSz = 0; ssh->processReplyState = PROCESS_INIT; ssh->error = 0; flatSeq[0] = (byte)(ssh->peerSeq >> 24); flatSeq[1] = (byte)(ssh->peerSeq >> 16); flatSeq[2] = (byte)(ssh->peerSeq >> 8); flatSeq[3] = (byte)(ssh->peerSeq); ret = wc_HmacInit(&hmac, ssh->ctx->heap, INVALID_DEVID); if (ret != WS_SUCCESS) { result = -203; goto done; } { byte digest[WC_MAX_DIGEST_SIZE]; ret = wc_HmacSetKey(&hmac, cases[i].hmacType, ssh->peerKeys.macKey, ssh->peerKeys.macKeySz); if (ret == WS_SUCCESS) ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq)); if (ret == WS_SUCCESS) ret = wc_HmacUpdate(&hmac, pkt, prefixLen); if (ret == WS_SUCCESS) ret = wc_HmacFinal(&hmac, digest); wc_HmacFree(&hmac); if (ret == WS_SUCCESS) WMEMCPY(pkt + prefixLen, digest, cases[i].macSz); } if (ret != WS_SUCCESS) { result = -204; goto done; } pkt[prefixLen] ^= 0x01; ShrinkBuffer(&ssh->inputBuffer, 1); ret = GrowBuffer(&ssh->inputBuffer, totalLen); if (ret != WS_SUCCESS) { result = -205; goto done; } WMEMCPY(ssh->inputBuffer.buffer, pkt, totalLen); ssh->inputBuffer.length = totalLen; ssh->inputBuffer.idx = 0; ret = wolfSSH_TestDoReceive(ssh); if (ret != WS_FATAL_ERROR) { result = -206; goto done; } if (ssh->error != WS_VERIFY_MAC_E) { result = -207; goto done; } } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_TEST_INTERNAL && any HMAC SHA variant enabled */ #ifdef WOLFSSH_TEST_INTERNAL /* Verify DoReceive rejects a binary packet whose padding_length is below the * RFC 4253 section 6 minimum of four bytes, returning WS_BUFFER_E. The packet * is delivered in the clear (no cipher, no MAC), matching the pre-key-exchange * transport, so DoPacket's padding check is what rejects it. */ static int test_DoReceive_RejectsShortPadding(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int ret; int result = 0; /* A well-formed MSGID_IGNORE packet carrying an empty string, but with * padding_length = 1 (below MIN_PAD_LENGTH). Aside from the short padding * the packet parses cleanly, so the padding check is the only thing that * can reject it. Layout: uint32 packet_length=7, padding_length=1, * msgId, uint32 string_len=0, 1 pad byte => 11 bytes total. */ byte pkt[11]; word32 totalLen = (word32)sizeof(pkt); ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -760; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -761; } pkt[0] = 0; pkt[1] = 0; pkt[2] = 0; pkt[3] = 7; /* packet_length */ pkt[4] = 1; /* padding_length, below MIN_PAD_LENGTH (4) */ pkt[5] = MSGID_IGNORE; pkt[6] = 0; pkt[7] = 0; pkt[8] = 0; pkt[9] = 0; /* string_len = 0 */ pkt[10] = 0; /* padding */ ssh->peerEncryptId = ID_NONE; ssh->peerAeadMode = 0; ssh->peerBlockSz = MIN_BLOCK_SZ; ssh->peerMacId = ID_NONE; ssh->peerMacSz = 0; ssh->peerSeq = 0; ssh->curSz = 0; ssh->processReplyState = PROCESS_INIT; ssh->error = 0; ShrinkBuffer(&ssh->inputBuffer, 1); ret = GrowBuffer(&ssh->inputBuffer, totalLen); if (ret != WS_SUCCESS) { result = -762; goto done2; } WMEMCPY(ssh->inputBuffer.buffer, pkt, totalLen); ssh->inputBuffer.length = totalLen; ssh->inputBuffer.idx = 0; ret = wolfSSH_TestDoReceive(ssh); if (ret != WS_FATAL_ERROR) { result = -763; goto done2; } if (ssh->error != WS_BUFFER_E) { result = -764; goto done2; } done2: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_TEST_INTERNAL */ #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256) typedef struct { const char* candidate; const char* generator; word32 minBits; word32 maxBits; int expectedResult; } PrimeTestVector; static const PrimeTestVector primeTestVectors[] = { { /* * For testing the ValidateKexDhGexGroup() function, we need to * verify that the function detects unsafe primes. The following * unsafe prime is the prime used with GOST-ECC. (RFC 7836) It is * prime and fine for its application. It isn't safe for DH, as * q = (p-1)/2 is not prime. */ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc7", "02", 512, 8192, WS_CRYPTO_FAILED }, { /* * We need to verify that the function detects safe primes. The * following safePrime is the MODP 2048-bit group from RFC 3526. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", "02", 2048, 8192, WS_SUCCESS }, { /* * This checks for g = p - 1. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68fffffffffffffffe", 2048, 8192, WS_CRYPTO_FAILED }, { /* * This checks for g = 1. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", "01", 2048, 8192, WS_CRYPTO_FAILED }, { /* * This checks prime size less than minBits. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", "02", 3072, 8192, WS_DH_SIZE_E }, { /* * This checks prime size greater than maxBits. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", "02", 512, 1024, WS_DH_SIZE_E }, { /* * This checks for even p. */ "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" "3995497cea956ae515d2261898fa051015728e5a8aacaa68fffffffffffffffe", "02", 2048, 8192, WS_CRYPTO_FAILED }, { /* * A well known composite number that breaks some MR implementations. * This is calculated by wolfCrypt for one of its prime tests. */ "000000000088cbf655be37a612fa535b4a9b81d394854ecbedfe1a4afbecdc7b" "a6a263549dd3c17882b054329384962576e7c5aa281e04ab5a0e7245584ad324" "9c7ac4de7caf5663bae95f6bb9e8bec4124e04d82eac54a246bda49a5c5c2a1b" "366ef8c085fc7c5f87478a55832d1b2184154c24260df67561d17c4359724403", "02", 512, 8192, WS_CRYPTO_FAILED }, }; static int test_DhGexGroupValidate(void) { WC_RNG rng; const PrimeTestVector* tv; byte* candidate; byte* generator; word32 candidateSz; word32 generatorSz; int tc = (int)(sizeof(primeTestVectors)/sizeof(primeTestVectors[0])); int result = 0, ret, i; if (wc_InitRng(&rng) != 0) { printf("DhGexGroupValidate: wc_InitRng failed\n"); return -110; } for (i = 0, tv = primeTestVectors; i < tc && !result; i++, tv++) { candidate = NULL; candidateSz = 0; generator = NULL; generatorSz = 0; ret = ConvertHexToBin(tv->candidate, &candidate, &candidateSz, tv->generator, &generator, &generatorSz, NULL, NULL, NULL, NULL, NULL, NULL); if (ret != 0) { result = -113; break; } ret = wolfSSH_TestValidateKexDhGexGroup(candidate, candidateSz, generator, generatorSz, tv->minBits, tv->maxBits, &rng); if (ret != tv->expectedResult) { printf("DhGexGroupValidate: validator returned %d, expected %d\n", ret, tv->expectedResult); result = -121; } FreeBins(candidate, generator, NULL, NULL); } wc_FreeRng(&rng); return result; } #endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_DH_GEX_SHA256 */ #ifdef WOLFSSH_TEST_INTERNAL /* Verify DoUserAuthBanner fully consumes the payload, including a non-empty * language tag. Before the fix, the tag's data bytes were left unconsumed, * which would misalign packet decoding for subsequent messages. */ static int test_DoUserAuthBanner(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int result = 0; /* Payload layout: [4-byte banner len][banner][4-byte lang len][lang] */ struct { const char* banner; word32 bannerSz; const char* lang; word32 langSz; int expectRet; const char* label; } cases[] = { { "Welcome", 7, "", 0, WS_SUCCESS, "empty lang tag" }, { "Welcome", 7, "en-US", 5, WS_SUCCESS, "non-empty lang tag" }, { NULL, 0, NULL, 0, WS_BAD_ARGUMENT, "null ssh" }, }; int i; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -300; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -301; } ctx->showBanner = 0; for (i = 0; i < (int)(sizeof(cases)/sizeof(cases[0])); i++) { byte buf[128]; word32 idx = 0; word32 len = 0; int ret; if (cases[i].banner == NULL) { /* null-ssh case: pass NULL ssh, dummy non-null buf */ buf[0] = 0; len = 1; ret = wolfSSH_TestDoUserAuthBanner(NULL, buf, len, &idx); } else { /* encode banner string */ buf[len++] = (byte)(cases[i].bannerSz >> 24); buf[len++] = (byte)(cases[i].bannerSz >> 16); buf[len++] = (byte)(cases[i].bannerSz >> 8); buf[len++] = (byte)(cases[i].bannerSz); WMEMCPY(buf + len, cases[i].banner, cases[i].bannerSz); len += cases[i].bannerSz; /* encode language tag string */ buf[len++] = (byte)(cases[i].langSz >> 24); buf[len++] = (byte)(cases[i].langSz >> 16); buf[len++] = (byte)(cases[i].langSz >> 8); buf[len++] = (byte)(cases[i].langSz); WMEMCPY(buf + len, cases[i].lang, cases[i].langSz); len += cases[i].langSz; ret = wolfSSH_TestDoUserAuthBanner(ssh, buf, len, &idx); } if (ret != cases[i].expectRet) { printf("DoUserAuthBanner[%s]: ret=%d, expected=%d\n", cases[i].label, ret, cases[i].expectRet); result = -302 - i; break; } /* On success the entire payload must be consumed. */ if (ret == WS_SUCCESS && idx != len) { printf("DoUserAuthBanner[%s]: idx=%u, len=%u (unconsumed bytes)\n", cases[i].label, idx, len); result = -310 - i; break; } } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_SCP) /* Verify GetScpFileMode strips setuid/setgid/sticky bits from a peer-supplied * SCP C/D record mode, matching the masking already done on the send path. * The receive path cannot be exercised end-to-end because both peers mask the * mode before transmitting, so this drives the parser directly. */ static int test_ScpGetFileMode(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; static const char* hdrs[] = { "C4755 0 f\n", /* setuid set */ "D2755 0 d\n", /* setgid set */ "D1755 0 d\n", /* sticky set */ "D7777 0 d\n", /* all special bits set */ "C0644 0 f\n" /* ordinary mode, unaffected */ }; static const int expected[] = { 0755, 0755, 0755, 0777, 0644 }; /* records the parser must reject */ static const char* badHdrs[] = { "C8755 0 f\n", /* '8' is not an octal digit */ "X4755 0 f\n", /* prefix is neither 'C' nor 'D' */ "C75" /* shorter than the mode field */ }; int result = 0; int ret; int i; word32 idx; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -420; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -421; } for (i = 0; i < (int)(sizeof(hdrs) / sizeof(hdrs[0])); i++) { idx = 0; ssh->scpFileMode = 0; ret = wolfSSH_TestScpGetFileMode(ssh, (byte*)hdrs[i], (word32)WSTRLEN(hdrs[i]), &idx); if (ret != WS_SUCCESS) { result = -422; break; } if (ssh->scpFileMode != expected[i]) { result = -423; break; } /* index advances past the 'C'/'D', four mode octets, and the * trailing space */ if (idx != 6) { result = -424; break; } } for (i = 0; result == 0 && i < (int)(sizeof(badHdrs) / sizeof(badHdrs[0])); i++) { idx = 0; ret = wolfSSH_TestScpGetFileMode(ssh, (byte*)badHdrs[i], (word32)WSTRLEN(badHdrs[i]), &idx); if (ret != WS_BAD_ARGUMENT) { result = -425; break; } } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_TEST_INTERNAL && WOLFSSH_SCP */ static int test_ChannelPutData(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* channel = NULL; byte data[110]; int result = 0; int ret; WMEMSET(data, 0xAB, sizeof(data)); ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -400; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -401; } /* Window of 100 bytes, matching the input buffer size. */ channel = ChannelNew(ssh, ID_CHANTYPE_SESSION, 100, 100); if (channel == NULL) { wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return -402; } /* NULL channel */ ret = wolfSSH_TestChannelPutData(NULL, data, 10); if (ret != WS_BAD_ARGUMENT) { result = -403; goto done; } /* NULL data */ ret = wolfSSH_TestChannelPutData(channel, NULL, 10); if (ret != WS_BAD_ARGUMENT) { result = -404; goto done; } /* dataSz exceeds windowSz: 101 > 100 */ ret = wolfSSH_TestChannelPutData(channel, data, 101); if (ret != WS_FATAL_ERROR) { result = -405; goto done; } /* Valid write consuming half the window */ ret = wolfSSH_TestChannelPutData(channel, data, 50); if (ret != WS_SUCCESS) { result = -406; goto done; } /* Remaining windowSz is 50; sending 51 must be rejected */ ret = wolfSSH_TestChannelPutData(channel, data, 51); if (ret != WS_FATAL_ERROR) { result = -407; goto done; } done: ChannelDelete(channel, ctx->heap); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* Counter callback for test_MsgHighwater. Records each invocation without * triggering wolfSSH_TriggerKeyExchange (which needs a live session). */ typedef struct HwTestCtx { int count; byte lastSide; } HwTestCtx; static int HwTestCb(byte side, void* ctx) { HwTestCtx* hc = (HwTestCtx*)ctx; if (hc != NULL) { hc->count++; hc->lastSide = side; } return WS_SUCCESS; } /* Exercise the wolfSSH_*MsgHighwater APIs and the per-key packet-count * threshold path inside HighwaterCheck. Covers: * - NULL safety on getters/setters * - CTX default value matches WOLFSSH_DEFAULT_MSG_HIGHWATER_MARK * - CTX/SSH setter round-trip and CTX -> SSH inheritance on wolfSSH_new * - SSH setter does not bleed back into the CTX * - Threshold crossing fires the highwater callback exactly once per epoch * (msgHighwaterFlag gates re-firing under the same keys) * - Receive side fires independently of the transmit side * - msgHighwaterMark == 0 disables the per-key packet check */ static int test_MsgHighwater(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; HwTestCtx hc; int result = 0; if (wolfSSH_GetMsgHighwater(NULL) != 0) return -800; wolfSSH_CTX_SetMsgHighwater(NULL, 1234); wolfSSH_SetMsgHighwater(NULL, 1234); ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -801; if (ctx->msgHighwaterMark != WOLFSSH_DEFAULT_MSG_HIGHWATER_MARK) { result = -802; goto done; } wolfSSH_CTX_SetMsgHighwater(ctx, 4096); if (ctx->msgHighwaterMark != 4096) { result = -803; goto done; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -804; goto done; } if (wolfSSH_GetMsgHighwater(ssh) != 4096) { result = -805; goto done; } wolfSSH_SetMsgHighwater(ssh, 16); if (wolfSSH_GetMsgHighwater(ssh) != 16) { result = -806; goto done; } if (ctx->msgHighwaterMark != 4096) { result = -807; goto done; } /* Install a counter callback. ssh->highwaterMark stays at the inherited * default (~1 GiB) and txCount/rxCount are not touched, so the byte-count * branch cannot fire and only the packet-count branch is under test. */ WMEMSET(&hc, 0, sizeof(hc)); wolfSSH_SetHighwaterCb(ctx, ctx->highwaterMark, HwTestCb); wolfSSH_SetHighwaterCtx(ssh, &hc); wolfSSH_SetMsgHighwater(ssh, 8); ssh->txMsgCount = 7; if (wolfSSH_TestHighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT) != WS_SUCCESS || hc.count != 0) { result = -808; goto done; } ssh->txMsgCount = 8; if (wolfSSH_TestHighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT) != WS_SUCCESS || hc.count != 1 || hc.lastSide != WOLFSSH_HWSIDE_TRANSMIT) { result = -809; goto done; } /* Flag-gated: further packets in the same epoch must not re-fire. */ ssh->txMsgCount = 100; if (wolfSSH_TestHighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT) != WS_SUCCESS || hc.count != 1) { result = -810; goto done; } /* Simulate a fresh key epoch (msgHighwaterFlag and rx/txMsgCount are * reset by DoNewKeys/SendNewKeys) and verify the receive side fires. */ ssh->msgHighwaterFlag = 0; ssh->rxMsgCount = 8; if (wolfSSH_TestHighwaterCheck(ssh, WOLFSSH_HWSIDE_RECEIVE) != WS_SUCCESS || hc.count != 2 || hc.lastSide != WOLFSSH_HWSIDE_RECEIVE) { result = -811; goto done; } /* mark == 0 disables the per-key packet check entirely. */ wolfSSH_SetMsgHighwater(ssh, 0); ssh->msgHighwaterFlag = 0; ssh->txMsgCount = 0xFFFFFFFFu; ssh->rxMsgCount = 0xFFFFFFFFu; if (wolfSSH_TestHighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT) != WS_SUCCESS || hc.count != 2) { result = -812; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int test_DoChannelSuccess(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; word32 idx; /* Short buffer: only 3 bytes, GetUint32 needs 4. */ static const byte payShort[] = { 0x00, 0x00, 0x00 }; /* Unknown channel id = 99 (0x63). */ static const byte payUnknown[] = { 0x00, 0x00, 0x00, 0x63 }; /* Happy path: channel id = 0. */ static const byte payOk[] = { 0x00, 0x00, 0x00, 0x00 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -500; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -501; goto done; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ); if (ch == NULL) { result = -502; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -503; goto done; } /* Short buffer -> WS_BUFFER_E */ idx = 0; ret = wolfSSH_TestDoChannelSuccess(ssh, (byte*)payShort, (word32)sizeof(payShort), &idx); if (ret != WS_BUFFER_E) { result = -510; goto done; } if (idx != 0) { result = -514; goto done; } /* Unknown channel -> WS_INVALID_CHANID */ idx = 0; ret = wolfSSH_TestDoChannelSuccess(ssh, (byte*)payUnknown, (word32)sizeof(payUnknown), &idx); if (ret != WS_INVALID_CHANID) { result = -511; goto done; } if (idx != 4) { result = -515; goto done; } /* Happy path -> WS_SUCCESS, serverState == SERVER_DONE */ idx = 0; ret = wolfSSH_TestDoChannelSuccess(ssh, (byte*)payOk, (word32)sizeof(payOk), &idx); if (ret != WS_SUCCESS) { result = -512; goto done; } if (ssh->serverState != SERVER_DONE) { result = -513; goto done; } if (idx != 4) { result = -516; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int test_DoChannelFailure(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; word32 idx; static const byte payShort[] = { 0x00, 0x00, 0x00 }; static const byte payUnknown[] = { 0x00, 0x00, 0x00, 0x63 }; static const byte payOk[] = { 0x00, 0x00, 0x00, 0x00 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -520; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -521; goto done; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ); if (ch == NULL) { result = -522; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -523; goto done; } /* Short buffer -> WS_BUFFER_E */ idx = 0; ret = wolfSSH_TestDoChannelFailure(ssh, (byte*)payShort, (word32)sizeof(payShort), &idx); if (ret != WS_BUFFER_E) { result = -530; goto done; } if (idx != 0) { result = -533; goto done; } /* Unknown channel -> WS_INVALID_CHANID */ idx = 0; ret = wolfSSH_TestDoChannelFailure(ssh, (byte*)payUnknown, (word32)sizeof(payUnknown), &idx); if (ret != WS_INVALID_CHANID) { result = -531; goto done; } if (idx != 4) { result = -534; goto done; } /* Happy path -> WS_CHANOPEN_FAILED */ idx = 0; ret = wolfSSH_TestDoChannelFailure(ssh, (byte*)payOk, (word32)sizeof(payOk), &idx); if (ret != WS_CHANOPEN_FAILED) { result = -532; goto done; } if (idx != 4) { result = -535; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int test_DoChannelData_overflow(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; word32 idx; /* Channel id=0, dataSz=65 (> maxPacketSz of 64): overflow case. * Buffer holds header only; dataSz > maxPacketSz triggers the guard * before ChannelPutData is ever called. */ static const byte payOver[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0x00, 0x00, 0x00, 0x41, /* dataSz = 65 */ /* 65 payload bytes follow (all zeroes) */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* Channel id=0, dataSz=32 (< maxPacketSz of 64): within-limit case. */ static const byte payOk[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0x00, 0x00, 0x00, 0x20, /* dataSz = 32 */ /* 32 payload bytes */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -540; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -541; goto done; } /* windowSz=128, maxPacketSz=64 */ ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 128, 64); if (ch == NULL) { result = -542; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -543; goto done; } /* dataSz=65 > maxPacketSz=64 -> WS_RECV_OVERFLOW_E */ idx = 0; ret = wolfSSH_TestDoChannelData(ssh, (byte*)payOver, (word32)sizeof(payOver), &idx); if (ret != WS_RECV_OVERFLOW_E) { result = -550; goto done; } /* dataSz=32 <= maxPacketSz=64 -> WS_CHAN_RXD */ idx = 0; ret = wolfSSH_TestDoChannelData(ssh, (byte*)payOk, (word32)sizeof(payOk), &idx); if (ret != WS_CHAN_RXD) { result = -551; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int DiscardIoSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) { (void)ssh; (void)buf; (void)ctx; return (int)sz; } static int test_DoChannelExtendedData_overflow(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; word32 idx; /* channelId=0, dataTypeCode=1 (stderr), dataSz=65 (> maxPacketSz=64) */ static const byte payOver[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0x00, 0x00, 0x00, 0x01, /* dataTypeCode = 1 (stderr)*/ 0x00, 0x00, 0x00, 0x41, /* dataSz = 65 */ /* 65 payload bytes */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* channelId=0, dataTypeCode=1 (stderr), dataSz=32 (< maxPacketSz=64) */ static const byte payOk[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0x00, 0x00, 0x00, 0x01, /* dataTypeCode = 1 (stderr)*/ 0x00, 0x00, 0x00, 0x20, /* dataSz = 32 */ /* 32 payload bytes */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -580; wolfSSH_SetIOSend(ctx, DiscardIoSend); ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -581; goto done; } /* Allow MSGID_CHANNEL_WINDOW_ADJUST on this bare session. */ ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT; /* windowSz=128, maxPacketSz=64 */ ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 128, 64); if (ch == NULL) { result = -582; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -583; goto done; } /* dataSz=65 > maxPacketSz=64 -> WS_RECV_OVERFLOW_E */ idx = 0; ret = wolfSSH_TestDoChannelExtendedData(ssh, (byte*)payOver, (word32)sizeof(payOver), &idx); if (ret != WS_RECV_OVERFLOW_E) { result = -590; goto done; } /* dataSz=32 <= maxPacketSz=64 -> WS_EXTDATA */ idx = 0; ret = wolfSSH_TestDoChannelExtendedData(ssh, (byte*)payOk, (word32)sizeof(payOk), &idx); if (ret != WS_EXTDATA) { result = -591; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* DoChannelWindowAdjust adds the peer's advertised bytes to peerWindowSz. * A crafted bytesToAdd that would wrap the word32 must be rejected with * WS_OVERFLOW_E and leave the window untouched; a value that fits must be * applied. No other test exercises this handler. */ static int test_DoChannelWindowAdjust_overflow(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; word32 idx; /* channelId=0, bytesToAdd=0xFFFFFFFF (UINT32_MAX): wraps peerWindowSz. */ static const byte payOver[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0xFF, 0xFF, 0xFF, 0xFF /* bytesToAdd = UINT32_MAX */ }; /* channelId=0, bytesToAdd=0x40 (64): fits, advances the window. */ static const byte payOk[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0x00, 0x00, 0x00, 0x40 /* bytesToAdd = 64 */ }; /* channelId=0, bytesToAdd=UINT32_MAX-1024 (0xFFFFFBFF): the largest value * a 1024-byte window accepts. Fills peerWindowSz to exactly UINT32_MAX and * exercises the exact boundary of the > guard (a >= off-by-one rejects). */ static const byte payEdgeOk[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0xFF, 0xFF, 0xFB, 0xFF /* bytesToAdd = UINT32_MAX - 1024 */ }; /* channelId=0, bytesToAdd=UINT32_MAX-1024+1 (0xFFFFFC00): one past the * boundary, must overflow a 1024-byte window. */ static const byte payEdgeOver[] = { 0x00, 0x00, 0x00, 0x00, /* channelId = 0 */ 0xFF, 0xFF, 0xFC, 0x00 /* bytesToAdd = UINT32_MAX - 1024 + 1 */ }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -600; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -601; goto done; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ); if (ch == NULL) { result = -602; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -603; goto done; } /* Non-zero peer window so a UINT32_MAX adjustment would wrap it. */ ch->peerWindowSz = 1024; /* bytesToAdd = UINT32_MAX -> WS_OVERFLOW_E, window left unchanged. */ idx = 0; ret = wolfSSH_TestDoChannelWindowAdjust(ssh, (byte*)payOver, (word32)sizeof(payOver), &idx); if (ret != WS_OVERFLOW_E) { result = -610; goto done; } if (ch->peerWindowSz != 1024) { result = -611; goto done; } if (idx != 8) { result = -612; goto done; } /* bytesToAdd = 64 fits -> WS_SUCCESS, window advances by 64. */ idx = 0; ret = wolfSSH_TestDoChannelWindowAdjust(ssh, (byte*)payOk, (word32)sizeof(payOk), &idx); if (ret != WS_SUCCESS) { result = -613; goto done; } if (ch->peerWindowSz != 1024 + 64) { result = -614; goto done; } if (idx != 8) { result = -615; goto done; } /* Boundary +1: bytesToAdd = UINT32_MAX - 1024 + 1 overflows a 1024-byte * window -> WS_OVERFLOW_E, window left unchanged. */ ch->peerWindowSz = 1024; idx = 0; ret = wolfSSH_TestDoChannelWindowAdjust(ssh, (byte*)payEdgeOver, (word32)sizeof(payEdgeOver), &idx); if (ret != WS_OVERFLOW_E) { result = -616; goto done; } if (ch->peerWindowSz != 1024) { result = -617; goto done; } if (idx != 8) { result = -618; goto done; } /* Boundary: bytesToAdd = UINT32_MAX - 1024 is the largest value that fits * -> WS_SUCCESS, window filled to exactly UINT32_MAX. This is the case a * >= off-by-one in the guard would wrongly reject. */ ch->peerWindowSz = 1024; idx = 0; ret = wolfSSH_TestDoChannelWindowAdjust(ssh, (byte*)payEdgeOk, (word32)sizeof(payEdgeOk), &idx); if (ret != WS_SUCCESS) { result = -619; goto done; } if (ch->peerWindowSz != 0xFFFFFFFF) { result = -620; goto done; } if (idx != 8) { result = -621; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int test_SendChannelData_eofTxd(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int ret; byte buf[4] = { 0x00, 0x01, 0x02, 0x03 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -560; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -561; goto done; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ); if (ch == NULL) { result = -562; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -563; goto done; } ch->eofTxd = 1; /* SendChannelData after EOF -> WS_EOF */ ret = SendChannelData(ssh, ch->channel, buf, (word32)sizeof(buf)); if (ret != WS_EOF) { result = -570; goto done; } /* SendChannelExtendedData after EOF -> WS_EOF */ ret = SendChannelExtendedData(ssh, ch->channel, buf, (word32)sizeof(buf)); if (ret != WS_EOF) { result = -571; goto done; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* Plaintext SSH packet from IoSend (before encryption/MAC): LENGTH_SZ, * PAD_LENGTH_SZ, then payload starting with the message ID (RFC 4253; * wolfSSH PreparePacket/BundlePacket). Not for encrypted payloads or * arbitrary truncated chunks. */ static int CaptureMsgId(const byte* buf, word32 len) { word32 off = LENGTH_SZ + PAD_LENGTH_SZ; if (len <= off) return -1; return (int)buf[off]; } /* Verify DoChannelRequest sends CHANNEL_SUCCESS for known types and * CHANNEL_FAILURE for unrecognized ones (RFC 4254 Section 5.4). * * A custom IoSend callback captures the outgoing packet in plaintext * (no cipher negotiated on a fresh session). Message ID is read via * CaptureMsgId() using LENGTH_SZ + PAD_LENGTH_SZ. */ static byte s_chanReqCapture[256]; static word32 s_chanReqCaptureSz = 0; static int CaptureIoSendChanReq(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) { (void)ssh; (void)ctx; s_chanReqCaptureSz = (sz < (word32)sizeof(s_chanReqCapture)) ? sz : (word32)sizeof(s_chanReqCapture); WMEMCPY(s_chanReqCapture, buf, s_chanReqCaptureSz); return (int)sz; } static int test_DoChannelRequest(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; int i; /* Payloads: [uint32 channelId=0][string type][byte wantReply=1][extra] */ static const byte payShell[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x05, /* typeSz = 5 */ 0x73,0x68,0x65,0x6C,0x6C, /* "shell" */ 0x01 /* wantReply = 1 */ }; static const byte payExec[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x04, /* typeSz = 4 */ 0x65,0x78,0x65,0x63, /* "exec" */ 0x01, /* wantReply = 1 */ 0x00,0x00,0x00,0x02, /* cmdSz = 2 */ 0x6C,0x73 /* "ls" */ }; static const byte payUnknown[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x0C, /* typeSz = 12 */ 0x75,0x6E,0x6B,0x6E,0x6F,0x77, 0x6E,0x2D,0x74,0x79,0x70,0x65, /* "unknown-type" */ 0x01 /* wantReply = 1 */ }; struct { const char* label; const byte* payload; word32 payloadSz; int expectRet; byte expectMsgId; } cases[] = { { "shell", payShell, (word32)sizeof(payShell), WS_SUCCESS, MSGID_CHANNEL_SUCCESS }, { "exec", payExec, (word32)sizeof(payExec), WS_SUCCESS, MSGID_CHANNEL_SUCCESS }, { "unknown-type", payUnknown, (word32)sizeof(payUnknown), WS_SUCCESS, MSGID_CHANNEL_FAILURE }, }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -400; wolfSSH_SetIOSend(ctx, CaptureIoSendChanReq); ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -401; goto done; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ); if (ch == NULL) { result = -402; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -403; goto done; } for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) { word32 idx = 0; int ret; s_chanReqCaptureSz = 0; WMEMSET(s_chanReqCapture, 0, sizeof(s_chanReqCapture)); ret = wolfSSH_TestDoChannelRequest(ssh, (byte*)cases[i].payload, cases[i].payloadSz, &idx); if (ret != cases[i].expectRet) { printf("DoChannelRequest[%s]: ret=%d, expected=%d\n", cases[i].label, ret, cases[i].expectRet); result = -404 - i; goto done; } { int capMsgId = CaptureMsgId(s_chanReqCapture, s_chanReqCaptureSz); if (capMsgId < 0) { printf("DoChannelRequest[%s]: captured packet too short (%u)\n", cases[i].label, s_chanReqCaptureSz); result = -410 - i; goto done; } if (capMsgId != (int)cases[i].expectMsgId) { printf("DoChannelRequest[%s]: msg_id=0x%02x, expected=0x%02x\n", cases[i].label, capMsgId, cases[i].expectMsgId); result = -420 - i; goto done; } } } /* RFC 4254 sec 6.10: exit-status and exit-signal must not send a reply * even if the wire wantReply byte is 1. DoChannelRequest overrides * wantReply=0 for these types, so no CHANNEL_SUCCESS/FAILURE packet * should be emitted. */ #if defined(WOLFSSH_TERM) || defined(WOLFSSH_SHELL) { static const byte payExitStatus[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x0B, /* typeSz = 11 */ 0x65,0x78,0x69,0x74,0x2D, /* "exit-" */ 0x73,0x74,0x61,0x74,0x75,0x73, /* "status" */ 0x01, /* wantReply = 1 (wire) */ 0x00,0x00,0x00,0x00 /* exitStatus = 0 */ }; /* exit-signal: sigName="TERM", coreDumped=0, errorMsg="", * languageTag="" */ static const byte payExitSignal[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x0B, /* typeSz = 11 */ 0x65,0x78,0x69,0x74,0x2D, /* "exit-" */ 0x73,0x69,0x67,0x6E,0x61,0x6C, /* "signal" */ 0x01, /* wantReply = 1 (wire) */ 0x00,0x00,0x00,0x04, /* sigNameSz = 4 */ 0x54,0x45,0x52,0x4D, /* "TERM" */ 0x00, /* coreDumped = false */ 0x00,0x00,0x00,0x00, /* errorMsg = "" */ 0x00,0x00,0x00,0x00 /* languageTag = "" */ }; struct { const char* label; const byte* buf; word32 sz; int errBase; } noReplyCases[] = { { "exit-status", payExitStatus, (word32)sizeof(payExitStatus), -430 }, { "exit-signal", payExitSignal, (word32)sizeof(payExitSignal), -440 }, }; int k; for (k = 0; k < (int)(sizeof(noReplyCases)/sizeof(noReplyCases[0])); k++) { word32 idx2 = 0; int ret2; s_chanReqCaptureSz = 0; WMEMSET(s_chanReqCapture, 0, sizeof(s_chanReqCapture)); ret2 = wolfSSH_TestDoChannelRequest(ssh, (byte*)noReplyCases[k].buf, noReplyCases[k].sz, &idx2); if (ret2 != WS_SUCCESS) { printf("DoChannelRequest[%s]: ret=%d, expected=%d\n", noReplyCases[k].label, ret2, WS_SUCCESS); result = noReplyCases[k].errBase; goto done; } if (s_chanReqCaptureSz != 0) { printf("DoChannelRequest[%s]: unexpected reply packet " "(sz=%u)\n", noReplyCases[k].label, s_chanReqCaptureSz); result = noReplyCases[k].errBase - 1; goto done; } } } #endif /* WOLFSSH_TERM || WOLFSSH_SHELL */ /* RFC 4254 sec 6.7: window-change must not send a reply even if the * wire wantReply byte is 1. */ #if defined(WOLFSSH_SHELL) && defined(WOLFSSH_TERM) { static const byte payWindowChange[] = { 0x00,0x00,0x00,0x00, /* channelId = 0 */ 0x00,0x00,0x00,0x0D, /* typeSz = 13 */ 0x77,0x69,0x6E,0x64,0x6F,0x77,0x2D, /* "window-" */ 0x63,0x68,0x61,0x6E,0x67,0x65, /* "change" */ 0x01, /* wantReply = 1 (wire) */ 0x00,0x00,0x00,0x50, /* widthChar = 80 */ 0x00,0x00,0x00,0x18, /* heightRows = 24 */ 0x00,0x00,0x00,0x00, /* widthPixels = 0 */ 0x00,0x00,0x00,0x00 /* heightPixels = 0 */ }; word32 idx2 = 0; int ret2; s_chanReqCaptureSz = 0; WMEMSET(s_chanReqCapture, 0, sizeof(s_chanReqCapture)); ret2 = wolfSSH_TestDoChannelRequest(ssh, (byte*)payWindowChange, (word32)sizeof(payWindowChange), &idx2); if (ret2 != WS_SUCCESS) { printf("DoChannelRequest[window-change]: ret=%d, expected=%d\n", ret2, WS_SUCCESS); result = -450; goto done; } if (s_chanReqCaptureSz != 0) { printf("DoChannelRequest[window-change]: unexpected reply packet " "(sz=%u)\n", s_chanReqCaptureSz); result = -451; goto done; } } #endif /* WOLFSSH_SHELL && WOLFSSH_TERM */ done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* Capture buffer for the service-name unit test. Separate from the channel- * request capture so the two tests can run independently in any order. */ static byte s_authSvcCapture[256]; static word32 s_authSvcCaptureSz = 0; static word32 s_authSvcSendCount = 0; static int CaptureIoSendAuthSvc(WOLFSSH* ssh, void* buf, word32 sz, void* ctx) { (void)ssh; (void)ctx; s_authSvcCaptureSz = (sz < (word32)sizeof(s_authSvcCapture)) ? sz : (word32)sizeof(s_authSvcCapture); WMEMCPY(s_authSvcCapture, buf, s_authSvcCaptureSz); s_authSvcSendCount++; return (int)sz; } /* Verify DoUserAuthRequest rejects non-"ssh-connection" service names per * RFC 4252 Section 5. For each case we assert: * 1. ret == WS_SUCCESS (connection stays open for retry) * 2. SSH_MSG_USERAUTH_FAILURE is actually sent (see CaptureMsgId(): * LENGTH_SZ + PAD_LENGTH_SZ then msg id) * 3. *idx == len (entire payload consumed; buffer stays aligned) * * For invalid-service cases the auth-method field is intentionally omitted * from the payload. DoUserAuthRequest must short-circuit at the service-name * check and still satisfy all three assertions - proving it never tries to * parse the missing auth-method field. If the short-circuit were absent, * GetSize() for authNameSz would hit end-of-buffer and return WS_BUFFER_E, * failing assertion 1. * * For the valid-service case, auth method "xyz-unknown" (always unsupported * regardless of compile-time options) is included. The function reaches * auth-method dispatch, falls to the unknown-method else-branch, and sends * USERAUTH_FAILURE via that normal path. * * A second valid-service row appends fake password-style bytes after the * method name. That proves DoUserAuthRequest() consumes trailing * method-specific payload (begin = len in the unknown-method branch); without * it, DoReceive() could advance inputBuffer.idx short of the packet end and * misalign decoding. */ static const byte s_unknownAuthTrailingFakePassword[] = { 0x00, /* "change password" FALSE */ 0x00, 0x00, 0x00, 0x08, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', }; static int test_DoUserAuthRequest_serviceName(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int result = 0; struct { const char* svcName; word32 svcNameSz; const char* authMethod; /* NULL = omit field (proves short-circuit) */ word32 authMethodSz; int expectRet; const char* label; const byte* authTrailing; /* bytes after auth method; NULL if none */ word32 authTrailingSz; } cases[] = { /* valid service: auth dispatch fires, fails on unknown method */ { "ssh-connection", 14, "xyz-unknown", 11, WS_SUCCESS, "valid svc unknown auth", NULL, 0 }, /* same but trailing junk must be skipped so *idx reaches len */ { "ssh-connection", 14, "xyz-unknown", 11, WS_SUCCESS, "valid svc unknown auth trailing junk", s_unknownAuthTrailingFakePassword, (word32)sizeof(s_unknownAuthTrailingFakePassword) }, /* invalid service: short-circuit, auth-method field absent */ { "ssh-agent", 9, NULL, 0, WS_SUCCESS, "invalid ssh-agent svc", NULL, 0 }, { "bad", 3, NULL, 0, WS_SUCCESS, "invalid bad svc", NULL, 0 }, /* zero-length service name: NameToId("",0)==ID_UNKNOWN, must reject */ { "", 0, NULL, 0, WS_SUCCESS, "zero-length svc", NULL, 0 }, /* ssh-userauth: NameToId returns ID_SERVICE_USERAUTH, not * ID_SERVICE_CONNECTION, so must also be rejected */ { "ssh-userauth", 12, NULL, 0, WS_SUCCESS, "invalid ssh-userauth svc", NULL, 0 }, }; int i; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -500; wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc); for (i = 0; i < (int)(sizeof(cases)/sizeof(cases[0])); i++) { byte buf[128]; word32 len = 0, idx = 0; word32 snsz = cases[i].svcNameSz; int ret; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -501; goto done; } s_authSvcCaptureSz = 0; s_authSvcSendCount = 0; WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture)); /* username: "user" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 4; WMEMCPY(buf + len, "user", 4); len += 4; /* service name */ buf[len++] = (byte)(snsz >> 24); buf[len++] = (byte)(snsz >> 16); buf[len++] = (byte)(snsz >> 8); buf[len++] = (byte)snsz; if (snsz > 0) { WMEMCPY(buf + len, cases[i].svcName, snsz); } len += snsz; /* auth method: omit for invalid-service cases to prove short-circuit */ if (cases[i].authMethod != NULL) { word32 amsz = cases[i].authMethodSz; buf[len++] = (byte)(amsz >> 24); buf[len++] = (byte)(amsz >> 16); buf[len++] = (byte)(amsz >> 8); buf[len++] = (byte)amsz; WMEMCPY(buf + len, cases[i].authMethod, amsz); len += amsz; if (cases[i].authTrailingSz > 0U) { WMEMCPY(buf + len, cases[i].authTrailing, cases[i].authTrailingSz); len += cases[i].authTrailingSz; } } ret = wolfSSH_TestDoUserAuthRequest(ssh, buf, len, &idx); if (s_authSvcSendCount != 1) { printf("DoUserAuthRequest_svcName[%s]: expected 1 send, got %u\n", cases[i].label, s_authSvcSendCount); result = -540 - i; goto done; } if (ret != cases[i].expectRet) { printf("DoUserAuthRequest_svcName[%s]: ret=%d expected=%d\n", cases[i].label, ret, cases[i].expectRet); result = -502 - i; goto done; } /* MSGID_USERAUTH_FAILURE must be in the captured packet. */ { int capMsgId = CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz); if (capMsgId < 0 || capMsgId != MSGID_USERAUTH_FAILURE) { printf("DoUserAuthRequest_svcName[%s]: USERAUTH_FAILURE not " "sent (capSz=%u msg_id=0x%02x)\n", cases[i].label, s_authSvcCaptureSz, capMsgId >= 0 ? capMsgId : 0); result = -520 - i; goto done; } } /* All cases must consume the entire payload. */ if (idx != len) { printf("DoUserAuthRequest_svcName[%s]: idx=%u expected len=%u\n", cases[i].label, idx, len); result = -510 - i; goto done; } /* Invalid-service cases must NOT record the username. */ if (cases[i].authMethod == NULL && ssh->userName != NULL) { printf("DoUserAuthRequest_svcName[%s]: userName set on invalid " "service (expected NULL)\n", cases[i].label); result = -530 - i; goto done; } wolfSSH_free(ssh); ssh = NULL; } done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* userauth callback that records whether it was invoked. Returns SUCCESS so * that, if it were ever reached for a password-change request, the request * would be (incorrectly) authenticated - making a missed rejection visible. */ static int s_pwChangeCbCalled = 0; static int UnitAuthAlwaysSucceed(byte authType, WS_UserAuthData* authData, void* ctx) { (void)authData; (void)ctx; if (authType == WOLFSSH_USERAUTH_PASSWORD) { s_pwChangeCbCalled = 1; } return WOLFSSH_USERAUTH_SUCCESS; } /* Verify DoUserAuthRequest rejects a password request that sets the * password-change flag (RFC 4252 Section 8: an expired password MUST NOT be * used for authentication). The request is otherwise well-formed and the * userauth callback would return SUCCESS, so a missing rejection would let the * old password authenticate. Asserts: * 1. ret == WS_SUCCESS (connection stays open for retry) * 2. the userauth callback is never invoked * 3. exactly one packet is sent and it is SSH_MSG_USERAUTH_FAILURE * 4. *idx == len (the new-password field is fully consumed) */ static int test_DoUserAuthRequest_rejectsPasswordChange(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int result = 0; int ret; int capMsgId; byte buf[128]; word32 len = 0, idx = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -660; wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc); wolfSSH_SetUserAuth(ctx, UnitAuthAlwaysSucceed); ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -661; goto out; } s_pwChangeCbCalled = 0; s_authSvcCaptureSz = 0; s_authSvcSendCount = 0; WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture)); /* username: "user" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 4; WMEMCPY(buf + len, "user", 4); len += 4; /* service name: "ssh-connection" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 14; WMEMCPY(buf + len, "ssh-connection", 14); len += 14; /* auth method: "password" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 8; WMEMCPY(buf + len, "password", 8); len += 8; /* password-change flag: TRUE */ buf[len++] = 1; /* current password: "oldpass" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 7; WMEMCPY(buf + len, "oldpass", 7); len += 7; /* new password: "newpass" */ buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 7; WMEMCPY(buf + len, "newpass", 7); len += 7; ret = wolfSSH_TestDoUserAuthRequest(ssh, buf, len, &idx); if (ret != WS_SUCCESS) { result = -662; goto out; } if (s_pwChangeCbCalled) { /* The callback must not run for a password-change request. */ result = -663; goto out; } if (s_authSvcSendCount != 1) { result = -664; goto out; } capMsgId = CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz); if (capMsgId != MSGID_USERAUTH_FAILURE) { result = -665; goto out; } if (idx != len) { result = -666; goto out; } out: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* userAuthTypesCb that advertises no methods (returns mask 0). Mirrors a * wolfsshd configuration with both PasswordAuthentication no and * PubkeyAuthentication no. */ static int UnitAuthTypesReturnZero(WOLFSSH* ssh, void* ctx) { (void)ssh; (void)ctx; return 0; } /* Regression test for the 0-mask case (issue 4115 follow-up). When the * userAuthTypesCb advertises no methods, SendUserAuthFailure must still emit a * well-formed USERAUTH_FAILURE carrying an empty "authentications that can * continue" name-list (RFC 4252 Section 5.1) and return WS_SUCCESS, instead of * underflowing the name-list length to -1 and dropping the connection. * * Asserts: * 1. wolfSSH_TestSendUserAuthFailure() returns WS_SUCCESS (not negative). * 2. Exactly one packet is emitted (connection not dropped). * 3. The packet's message id is MSGID_USERAUTH_FAILURE. * 4. The name-list length field is 0 (empty method list). * * The control case (a permissive callback advertising publickey+password) * confirms the same path produces a non-empty name-list, so the empty result * is specific to the 0-mask input rather than the test always seeing 0. */ static int test_SendUserAuthFailure_emptyMethods(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int result = 0; int i; struct { WS_CallbackUserAuthTypes cb; int expectEmpty; /* 1 = name-list must be empty, 0 = non-empty */ const char* label; } cases[] = { { UnitAuthTypesReturnZero, 1, "no methods advertised" }, { NULL, 0, "default methods advertised" }, }; word32 off = LENGTH_SZ + PAD_LENGTH_SZ; for (i = 0; i < (int)(sizeof(cases)/sizeof(cases[0])); i++) { word32 nameListSz; int capMsgId; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -600; break; } if (cases[i].cb != NULL) { wolfSSH_SetUserAuthTypes(ctx, cases[i].cb); } wolfSSH_SetIOSend(ctx, CaptureIoSendAuthSvc); ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -601; break; } s_authSvcCaptureSz = 0; s_authSvcSendCount = 0; WMEMSET(s_authSvcCapture, 0, sizeof(s_authSvcCapture)); ret = wolfSSH_TestSendUserAuthFailure(ssh, 0); if (ret != WS_SUCCESS) { printf("SendUserAuthFailure[%s]: ret=%d expected WS_SUCCESS\n", cases[i].label, ret); result = -602 - i; break; } if (s_authSvcSendCount != 1) { printf("SendUserAuthFailure[%s]: expected 1 send, got %u\n", cases[i].label, s_authSvcSendCount); result = -610 - i; break; } capMsgId = CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz); if (capMsgId != MSGID_USERAUTH_FAILURE) { printf("SendUserAuthFailure[%s]: msgId=%d expected" " USERAUTH_FAILURE\n", cases[i].label, capMsgId); result = -620 - i; break; } /* name-list length is the 4 bytes following the message id */ if (s_authSvcCaptureSz < off + MSG_ID_SZ + LENGTH_SZ) { printf("SendUserAuthFailure[%s]: packet too short (%u)\n", cases[i].label, s_authSvcCaptureSz); result = -630 - i; break; } nameListSz = ((word32)s_authSvcCapture[off + MSG_ID_SZ] << 24) | ((word32)s_authSvcCapture[off + MSG_ID_SZ + 1] << 16) | ((word32)s_authSvcCapture[off + MSG_ID_SZ + 2] << 8) | ((word32)s_authSvcCapture[off + MSG_ID_SZ + 3]); if (cases[i].expectEmpty && nameListSz != 0) { printf("SendUserAuthFailure[%s]: nameListSz=%u expected 0\n", cases[i].label, nameListSz); result = -640 - i; break; } if (!cases[i].expectEmpty && nameListSz == 0) { printf("SendUserAuthFailure[%s]: nameListSz=0 expected non-empty\n", cases[i].label); result = -650 - i; break; } wolfSSH_free(ssh); ssh = NULL; wolfSSH_CTX_free(ctx); ctx = NULL; } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #if !defined(WOLFSSH_NO_RSA) /* 2048-bit RSA private key (PKCS#1 DER). * Same key as tests/auth.c hanselPrivateRsa - copied here so this * test has no dependency on WOLFSSH_KEYGEN. */ static const byte unitTestRsaPrivKey[] = { 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x3f, 0x76, 0x45, 0xa3, 0x03, 0xac, 0x38, 0xd5, 0xc7, 0x0f, 0x93, 0x30, 0x5a, 0x20, 0x9c, 0x89, 0x7c, 0xad, 0x05, 0x16, 0x46, 0x86, 0x83, 0x0d, 0x8a, 0x2b, 0x16, 0x4a, 0x05, 0x2c, 0xe4, 0x77, 0x47, 0x70, 0x00, 0xae, 0x1d, 0x83, 0xe2, 0xd9, 0x6e, 0x99, 0xd4, 0xf0, 0x45, 0x98, 0x15, 0x93, 0xf6, 0x87, 0x4e, 0xac, 0x64, 0x63, 0xa1, 0x95, 0xc9, 0x7c, 0x30, 0xe8, 0x3e, 0x2f, 0xa3, 0xf1, 0x24, 0x9f, 0x0c, 0x6b, 0x1c, 0xfe, 0x1b, 0x02, 0x99, 0xcd, 0xc6, 0xa7, 0x6c, 0x84, 0x85, 0x46, 0x54, 0x12, 0x40, 0xe1, 0xb4, 0xe5, 0xf2, 0xaa, 0x39, 0xec, 0xd6, 0x27, 0x24, 0x0b, 0xd1, 0xa1, 0xe2, 0xef, 0x34, 0x69, 0x25, 0x6d, 0xc0, 0x74, 0x67, 0x25, 0x98, 0x7d, 0xc4, 0xf8, 0x52, 0xab, 0x9b, 0x4b, 0x3a, 0x12, 0x1d, 0xe1, 0xe3, 0xfa, 0xd6, 0xcf, 0x9a, 0xe6, 0x9c, 0x23, 0x4e, 0x39, 0xc4, 0x84, 0x16, 0x88, 0x3d, 0x42, 0x4e, 0xd8, 0x2f, 0xcc, 0xd2, 0x91, 0x67, 0x9d, 0xb6, 0x71, 0x2a, 0x02, 0x65, 0x5f, 0xbb, 0x75, 0x0e, 0x8c, 0xbb, 0x87, 0x97, 0x97, 0xc6, 0xf8, 0xb2, 0x98, 0xe2, 0x2f, 0x68, 0x26, 0x4a, 0x53, 0xec, 0x79, 0x3a, 0x8a, 0x5f, 0xcc, 0xcf, 0xf0, 0x16, 0x47, 0xb2, 0xd0, 0x43, 0xd6, 0x36, 0x6c, 0xc8, 0xe7, 0x2f, 0xfe, 0xa7, 0x35, 0x39, 0x69, 0xfb, 0x1d, 0x78, 0x45, 0x9d, 0x89, 0x00, 0xc8, 0x41, 0xcf, 0x34, 0x1f, 0xa3, 0xf3, 0xf1, 0xfb, 0x28, 0x14, 0xfb, 0xd8, 0x48, 0x6f, 0xac, 0xe3, 0xfc, 0x33, 0xd1, 0xdb, 0xae, 0xef, 0x27, 0x9e, 0x57, 0x56, 0x29, 0xa2, 0x1a, 0x3a, 0xe5, 0x9a, 0xfe, 0xa4, 0x49, 0xc8, 0x7f, 0xb7, 0x4e, 0xd0, 0x1f, 0x04, 0x6e, 0x58, 0x16, 0xb7, 0xeb, 0x9d, 0xf8, 0x92, 0x3c, 0xc2, 0xb0, 0x21, 0x7c, 0x4e, 0x31, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8d, 0xa4, 0x61, 0x06, 0x2f, 0xc3, 0x40, 0xf4, 0x6c, 0xf4, 0x87, 0x30, 0xb8, 0x00, 0xcc, 0xe5, 0xbc, 0x75, 0x87, 0x1e, 0x06, 0x95, 0x14, 0x7a, 0x23, 0xf9, 0x24, 0xd4, 0x92, 0xe4, 0x1a, 0xbc, 0x88, 0x95, 0xfc, 0x3b, 0x56, 0x16, 0x1b, 0x2e, 0xff, 0x64, 0x2b, 0x58, 0xd7, 0xd8, 0x8e, 0xc2, 0x9f, 0xb2, 0xe5, 0x84, 0xb9, 0xbc, 0x8d, 0x61, 0x54, 0x35, 0xb0, 0x70, 0xfe, 0x72, 0x04, 0xc0, 0x24, 0x6d, 0x2f, 0x69, 0x61, 0x06, 0x1b, 0x1d, 0xe6, 0x2d, 0x6d, 0x79, 0x60, 0xb7, 0xf4, 0xdb, 0xb7, 0x4e, 0x97, 0x36, 0xde, 0x77, 0xc1, 0x9f, 0x85, 0x4e, 0xc3, 0x77, 0x69, 0x66, 0x2e, 0x3e, 0x61, 0x76, 0xf3, 0x67, 0xfb, 0xc6, 0x9a, 0xc5, 0x6f, 0x99, 0xff, 0xe6, 0x89, 0x43, 0x92, 0x44, 0x75, 0xd2, 0x4e, 0x54, 0x91, 0x58, 0xb2, 0x48, 0x2a, 0xe6, 0xfa, 0x0d, 0x4a, 0xca, 0xd4, 0x14, 0x9e, 0xf6, 0x27, 0x67, 0xb7, 0x25, 0x7a, 0x43, 0xbb, 0x2b, 0x67, 0xd1, 0xfe, 0xd1, 0x68, 0x23, 0x06, 0x30, 0x7c, 0xbf, 0x60, 0x49, 0xde, 0xcc, 0x7e, 0x26, 0x5a, 0x3b, 0xfe, 0xa6, 0xa6, 0xe7, 0xa8, 0xdd, 0xac, 0xb9, 0xaf, 0x82, 0x9a, 0x3a, 0x41, 0x7e, 0x61, 0x21, 0x37, 0xa3, 0x08, 0xe4, 0xc4, 0xbc, 0x11, 0xf5, 0x3b, 0x8e, 0x4d, 0x51, 0xf3, 0xbd, 0xda, 0xba, 0xb2, 0xc5, 0xee, 0xfb, 0xcf, 0xdf, 0x83, 0xa1, 0x82, 0x01, 0xe1, 0x51, 0x9d, 0x07, 0x5a, 0x5d, 0xd8, 0xc7, 0x5b, 0x3f, 0x97, 0x13, 0x6a, 0x4d, 0x1e, 0x8d, 0x39, 0xac, 0x40, 0x95, 0x82, 0x6c, 0xa2, 0xa1, 0xcc, 0x8a, 0x9b, 0x21, 0x32, 0x3a, 0x58, 0xcc, 0xe7, 0x2d, 0x1a, 0x79, 0xa4, 0x31, 0x50, 0xb1, 0x4b, 0x76, 0x23, 0x1b, 0xb3, 0x40, 0x3d, 0x3d, 0x72, 0x72, 0x32, 0xec, 0x5f, 0x38, 0xb5, 0x8d, 0xb2, 0x8d, 0x02, 0x81, 0x81, 0x00, 0xed, 0x5a, 0x7e, 0x8e, 0xa1, 0x62, 0x7d, 0x26, 0x5c, 0x78, 0xc4, 0x87, 0x71, 0xc9, 0x41, 0x57, 0x77, 0x94, 0x93, 0x93, 0x26, 0x78, 0xc8, 0xa3, 0x15, 0xbd, 0x59, 0xcb, 0x1b, 0xb4, 0xb2, 0x6b, 0x0f, 0xe7, 0x80, 0xf2, 0xfa, 0xfc, 0x8e, 0x32, 0xa9, 0x1b, 0x1e, 0x7f, 0xe1, 0x26, 0xef, 0x00, 0x25, 0xd8, 0xdd, 0xc9, 0x1a, 0x23, 0x00, 0x26, 0x3b, 0x46, 0x23, 0xc0, 0x50, 0xe7, 0xce, 0x62, 0xb2, 0x36, 0xb2, 0x98, 0x09, 0x16, 0x34, 0x18, 0x9e, 0x46, 0xbc, 0xaf, 0x2c, 0x28, 0x94, 0x2f, 0xe0, 0x5d, 0xc9, 0xb2, 0xc8, 0xfb, 0x5d, 0x13, 0xd5, 0x36, 0xaa, 0x15, 0x0f, 0x89, 0xa5, 0x16, 0x59, 0x5d, 0x22, 0x74, 0xa4, 0x47, 0x5d, 0xfa, 0xfb, 0x0c, 0x5e, 0x80, 0xbf, 0x0f, 0xc2, 0x9c, 0x95, 0x0f, 0xe7, 0xaa, 0x7f, 0x16, 0x1b, 0xd4, 0xdb, 0x38, 0x7d, 0x58, 0x2e, 0x57, 0x78, 0x2f, 0x02, 0x81, 0x81, 0x00, 0xcc, 0x1d, 0x7f, 0x74, 0x36, 0x6d, 0xb4, 0x92, 0x25, 0x62, 0xc5, 0x50, 0xb0, 0x5c, 0xa1, 0xda, 0xf3, 0xb2, 0xfd, 0x1e, 0x98, 0x0d, 0x8b, 0x05, 0x69, 0x60, 0x8e, 0x5e, 0xd2, 0x89, 0x90, 0x4a, 0x0d, 0x46, 0x7e, 0xe2, 0x54, 0x69, 0xae, 0x16, 0xe6, 0xcb, 0xd5, 0xbd, 0x7b, 0x30, 0x2b, 0x7b, 0x5c, 0xee, 0x93, 0x12, 0xcf, 0x63, 0x89, 0x9c, 0x3d, 0xc8, 0x2d, 0xe4, 0x7a, 0x61, 0x09, 0x5e, 0x80, 0xfb, 0x3c, 0x03, 0xb3, 0x73, 0xd6, 0x98, 0xd0, 0x84, 0x0c, 0x59, 0x9f, 0x4e, 0x80, 0xf3, 0x46, 0xed, 0x03, 0x9d, 0xd5, 0xdc, 0x8b, 0xe7, 0xb1, 0xe8, 0xaa, 0x57, 0xdc, 0xd1, 0x41, 0x55, 0x07, 0xc7, 0xdf, 0x67, 0x3c, 0x72, 0x78, 0xb0, 0x60, 0x8f, 0x85, 0xa1, 0x90, 0x99, 0x0c, 0xa5, 0x67, 0xab, 0xf0, 0xb6, 0x74, 0x90, 0x03, 0x55, 0x7b, 0x5e, 0xcc, 0xc5, 0xbf, 0xde, 0xa7, 0x9f, 0x02, 0x81, 0x80, 0x40, 0x81, 0x6e, 0x91, 0xae, 0xd4, 0x88, 0x74, 0xab, 0x7e, 0xfa, 0xd2, 0x60, 0x9f, 0x34, 0x8d, 0xe3, 0xe6, 0xd2, 0x30, 0x94, 0xad, 0x10, 0xc2, 0x19, 0xbf, 0x6b, 0x2e, 0xe2, 0xe9, 0xb9, 0xef, 0x94, 0xd3, 0xf2, 0xdc, 0x96, 0x4f, 0x9b, 0x09, 0xb3, 0xa1, 0xb6, 0x29, 0x44, 0xf4, 0x82, 0xd1, 0xc4, 0x77, 0x6a, 0xd7, 0x23, 0xae, 0x4d, 0x75, 0x16, 0x78, 0xda, 0x70, 0x82, 0xcc, 0x6c, 0xef, 0xaf, 0xc5, 0x63, 0xc6, 0x23, 0xfa, 0x0f, 0xd0, 0x7c, 0xfb, 0x76, 0x7e, 0x18, 0xff, 0x32, 0x3e, 0xcc, 0xb8, 0x50, 0x7f, 0xb1, 0x55, 0x77, 0x17, 0x53, 0xc3, 0xd6, 0x77, 0x80, 0xd0, 0x84, 0xb8, 0x4d, 0x33, 0x1d, 0x91, 0x1b, 0xb0, 0x75, 0x9f, 0x27, 0x29, 0x56, 0x69, 0xa1, 0x03, 0x54, 0x7d, 0x9f, 0x99, 0x41, 0xf9, 0xb9, 0x2e, 0x36, 0x04, 0x24, 0x4b, 0xf6, 0xec, 0xc7, 0x33, 0x68, 0x6b, 0x02, 0x81, 0x80, 0x60, 0x35, 0xcb, 0x3c, 0xd0, 0xe6, 0xf7, 0x05, 0x28, 0x20, 0x1d, 0x57, 0x82, 0x39, 0xb7, 0x85, 0x07, 0xf7, 0xa7, 0x3d, 0xc3, 0x78, 0x26, 0xbe, 0x3f, 0x44, 0x66, 0xf7, 0x25, 0x0f, 0xf8, 0x76, 0x1f, 0x39, 0xca, 0x57, 0x0e, 0x68, 0xdd, 0xc9, 0x27, 0xb2, 0x8e, 0xa6, 0x08, 0xa9, 0xd4, 0xe5, 0x0a, 0x11, 0xde, 0x3b, 0x30, 0x8b, 0xff, 0x72, 0x28, 0xe0, 0xf1, 0x58, 0xcf, 0xa2, 0x6b, 0x93, 0x23, 0x02, 0xc8, 0xf0, 0x09, 0xa7, 0x21, 0x50, 0xd8, 0x80, 0x55, 0x7d, 0xed, 0x0c, 0x48, 0xd5, 0xe2, 0xe9, 0x97, 0x19, 0xcf, 0x93, 0x6c, 0x52, 0xa2, 0xd6, 0x43, 0x6c, 0xb4, 0xc5, 0xe1, 0xa0, 0x9d, 0xd1, 0x45, 0x69, 0x58, 0xe1, 0xb0, 0x27, 0x9a, 0xec, 0x2b, 0x95, 0xd3, 0x1d, 0x81, 0x0b, 0x7a, 0x09, 0x5e, 0xa5, 0xf1, 0xdd, 0x6b, 0xe4, 0xe0, 0x08, 0xf8, 0x46, 0x81, 0xc1, 0x06, 0x8b, 0x02, 0x81, 0x80, 0x00, 0xf6, 0xf2, 0xeb, 0x25, 0xba, 0x78, 0x04, 0xad, 0x0e, 0x0d, 0x2e, 0xa7, 0x69, 0xd6, 0x57, 0xe6, 0x36, 0x32, 0x50, 0xd2, 0xf2, 0xeb, 0xad, 0x31, 0x46, 0x65, 0xc0, 0x07, 0x97, 0x83, 0x6c, 0x66, 0x27, 0x3e, 0x94, 0x2c, 0x05, 0x01, 0x5f, 0x5c, 0xe0, 0x31, 0x30, 0xec, 0x61, 0xd2, 0x74, 0x35, 0xb7, 0x9f, 0x38, 0xe7, 0x8e, 0x67, 0xb1, 0x50, 0x08, 0x68, 0xce, 0xcf, 0xd8, 0xee, 0x88, 0xfd, 0x5d, 0xc4, 0xcd, 0xe2, 0x86, 0x3d, 0x4a, 0x0e, 0x04, 0x7f, 0xee, 0x8a, 0xe8, 0x9b, 0x16, 0xa1, 0xfc, 0x09, 0x82, 0xe2, 0x62, 0x03, 0x3c, 0xe8, 0x25, 0x7f, 0x3c, 0x9a, 0xaa, 0x83, 0xf8, 0xd8, 0x93, 0xd1, 0x54, 0xf9, 0xce, 0xb4, 0xfa, 0x35, 0x36, 0xcc, 0x18, 0x54, 0xaa, 0xf2, 0x90, 0xb7, 0x7c, 0x97, 0x0b, 0x27, 0x2f, 0xae, 0xfc, 0xc3, 0x93, 0xaf, 0x1a, 0x75, 0xec, 0x18, 0xdb }; static const word32 unitTestRsaPrivKeySz = (word32)sizeof(unitTestRsaPrivKey); #endif /* WOLFSSH_NO_RSA */ /* Keys for test_IdentifyAsn1Key: inline DER so the test is self-contained * in both filesystem and NO_FILESYSTEM builds. Each array matches the * corresponding file under keys/. */ #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 /* keys/server-key-ecc.der - P-256 RFC-5915 ECPrivateKey */ static const byte unitTestEcc256PrivKey[] = { 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x61, 0x09, 0x99, 0x0B, 0x79, 0xD2, 0x5F, 0x28, 0x5A, 0x0F, 0x5D, 0x15, 0xCC, 0xA1, 0x56, 0x54, 0xF9, 0x2B, 0x39, 0x87, 0x21, 0x2D, 0xA7, 0x7D, 0x85, 0x7B, 0xB8, 0x7F, 0x38, 0xC6, 0x6D, 0xD5, 0xA0, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0xA1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x81, 0x13, 0xFF, 0xA4, 0x2B, 0xB7, 0x9C, 0x45, 0x74, 0x7A, 0x83, 0x4C, 0x61, 0xF3, 0x3F, 0xAD, 0x26, 0xCF, 0x22, 0xCD, 0xA9, 0xA3, 0xBC, 0xA5, 0x61, 0xB4, 0x7C, 0xE6, 0x62, 0xD4, 0xC2, 0xF7, 0x55, 0x43, 0x9A, 0x31, 0xFB, 0x80, 0x11, 0x20, 0xB5, 0x12, 0x4B, 0x24, 0xF5, 0x78, 0xD7, 0xFD, 0x22, 0xEF, 0x46, 0x35, 0xF0, 0x05, 0x58, 0x6B, 0x5F, 0x63, 0xC8, 0xDA, 0x1B, 0xC4, 0xF5, 0x69 }; #endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */ #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384 /* keys/server-key-ecc-384.der - P-384 RFC-5915 ECPrivateKey */ static const byte unitTestEcc384PrivKey[] = { 0x30, 0x81, 0xA4, 0x02, 0x01, 0x01, 0x04, 0x30, 0x3E, 0xAD, 0xD2, 0xBB, 0xBF, 0x05, 0xA7, 0xBE, 0x3A, 0x3F, 0x7C, 0x28, 0x15, 0x12, 0x89, 0xDE, 0x5B, 0xB3, 0x64, 0x4D, 0x70, 0x11, 0x76, 0x1D, 0xB5, 0x6F, 0x2A, 0x03, 0x62, 0xFB, 0xA6, 0x4F, 0x98, 0xE6, 0x4F, 0xF9, 0x86, 0xDC, 0x4F, 0xB8, 0xEF, 0xDB, 0x2D, 0x6B, 0x8D, 0xA5, 0x71, 0x42, 0xA0, 0x07, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22, 0xA1, 0x64, 0x03, 0x62, 0x00, 0x04, 0x38, 0xD6, 0x2B, 0xE4, 0x18, 0xFF, 0x57, 0x3F, 0xD0, 0xE0, 0x20, 0xD4, 0x88, 0x76, 0xC4, 0xE1, 0x12, 0x1D, 0xFB, 0x2D, 0x6E, 0xBE, 0xE4, 0x89, 0x5D, 0x77, 0x24, 0x31, 0x6D, 0x46, 0xA2, 0x31, 0x05, 0x87, 0x3F, 0x29, 0x86, 0xD5, 0xC7, 0x12, 0x80, 0x3A, 0x6F, 0x47, 0x1A, 0xB8, 0x68, 0x50, 0xEB, 0x06, 0x3E, 0x10, 0x89, 0x61, 0x34, 0x9C, 0xF8, 0xB4, 0xC6, 0xA4, 0xCF, 0x5E, 0x97, 0xBD, 0x7E, 0x51, 0xE9, 0x75, 0xE3, 0xE9, 0x21, 0x72, 0x61, 0x50, 0x6E, 0xB9, 0xCF, 0x3C, 0x49, 0x3D, 0x3E, 0xB8, 0x8D, 0x46, 0x7B, 0x5F, 0x27, 0xEB, 0xAB, 0x21, 0x61, 0xC0, 0x00, 0x66, 0xFE, 0xBD }; #endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP384 */ #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 /* keys/server-key-ecc-521.der - P-521 RFC-5915 ECPrivateKey */ static const byte unitTestEcc521PrivKey[] = { 0x30, 0x81, 0xDC, 0x02, 0x01, 0x01, 0x04, 0x42, 0x00, 0x4C, 0xA4, 0xD8, 0x64, 0x28, 0xD9, 0x40, 0x0E, 0x7B, 0x2D, 0xF3, 0x91, 0x2E, 0xB9, 0x96, 0xC1, 0x95, 0x89, 0x50, 0x43, 0xAF, 0x92, 0xE8, 0x6D, 0xE7, 0x0A, 0xE4, 0xDF, 0x46, 0xF2, 0x2A, 0x29, 0x1A, 0x6B, 0xB2, 0x74, 0x8A, 0xAE, 0x82, 0x58, 0x0D, 0xF6, 0xC3, 0x9F, 0x49, 0xB3, 0xED, 0x82, 0xF1, 0x78, 0x9E, 0xCE, 0x1B, 0x65, 0x7D, 0x45, 0x43, 0x8C, 0xFF, 0x15, 0x65, 0x34, 0x35, 0x45, 0x75, 0xA0, 0x07, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23, 0xA1, 0x81, 0x89, 0x03, 0x81, 0x86, 0x00, 0x04, 0x01, 0xF8, 0xD0, 0xA7, 0xC3, 0xC5, 0x8D, 0x84, 0x19, 0x57, 0x96, 0x9F, 0x21, 0x3A, 0x94, 0xF3, 0xDA, 0x55, 0x0E, 0xDF, 0x76, 0xD8, 0xDD, 0x17, 0x15, 0x31, 0xF3, 0x5B, 0xB0, 0x69, 0xC8, 0xBC, 0x30, 0x0D, 0x6F, 0x6B, 0x37, 0xD1, 0x80, 0x46, 0xA9, 0x71, 0x7F, 0x2C, 0x6F, 0x59, 0x51, 0x9C, 0x82, 0x70, 0x95, 0xB2, 0x9A, 0x63, 0x13, 0x30, 0x62, 0x18, 0xC2, 0x35, 0x76, 0x94, 0x00, 0xD0, 0xF9, 0x6D, 0x00, 0x0A, 0x19, 0x3B, 0xA3, 0x46, 0x65, 0x2B, 0xEB, 0x40, 0x9A, 0x9A, 0x45, 0xC5, 0x97, 0xA3, 0xED, 0x93, 0x2D, 0xD5, 0xAA, 0xAE, 0x96, 0xBF, 0x2F, 0x31, 0x7E, 0x5A, 0x7A, 0xC7, 0x45, 0x8B, 0x3C, 0x6C, 0xDB, 0xAA, 0x90, 0xC3, 0x55, 0x38, 0x2C, 0xDF, 0xCD, 0xCA, 0x73, 0x77, 0xD9, 0x2E, 0xB2, 0x0A, 0x5E, 0x8C, 0x74, 0x23, 0x7C, 0xA5, 0xA3, 0x45, 0xB1, 0x9E, 0x3F, 0x1A, 0x22, 0x90, 0xB1, 0x54 }; #endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP521 */ #if !defined(WOLFSSH_NO_ED25519) /* keys/server-key-ed25519.der - Ed25519 OneAsymmetricKey (RFC 8410) */ static const byte unitTestEd25519PrivKey[] = { 0x30, 0x50, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20, 0x6a, 0x67, 0xf3, 0x0e, 0x64, 0xea, 0x52, 0xfe, 0xf4, 0xad, 0x65, 0x4d, 0x45, 0x60, 0x61, 0x38, 0x58, 0x11, 0x07, 0x84, 0xf0, 0x03, 0x94, 0x93, 0x14, 0x7b, 0x7b, 0x33, 0x1a, 0xba, 0xf6, 0x19, 0x81, 0x20, 0x0f, 0x56, 0x0c, 0x9f, 0x7d, 0x7a, 0x62, 0x87, 0xf0, 0x26, 0x16, 0x19, 0x31, 0xe4, 0xb2, 0x1d, 0xe9, 0xbd, 0xee, 0x4a, 0x7f, 0x55, 0xae, 0x26, 0x2d, 0xa1, 0x25, 0xe4, 0xee, 0x4a, 0x51, 0x00 }; #endif /* !WOLFSSH_NO_ED25519 */ #if !defined(WOLFSSH_NO_MLDSA) /* keys/server-key-mldsa44.der - MlDsa44 OneAsymmetricKey */ static const byte unitTestMlDsaPrivKey[] = { 0x30, 0x82, 0x0a, 0x3e, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x04, 0x82, 0x0a, 0x2a, 0x30, 0x82, 0x0a, 0x26, 0x04, 0x20, 0x07, 0x99, 0x36, 0x30, 0xd1, 0xef, 0x77, 0x3e, 0x75, 0x79, 0xbc, 0x3f, 0xb5, 0x78, 0xfa, 0x10, 0x26, 0x77, 0x79, 0x27, 0x19, 0x34, 0xf7, 0x68, 0x83, 0xce, 0x08, 0xb6, 0xbb, 0xe9, 0x06, 0x18, 0x04, 0x82, 0x0a, 0x00, 0x9d, 0x7b, 0x66, 0x85, 0x9a, 0xb3, 0xcb, 0xdf, 0x19, 0xc5, 0xaa, 0xe2, 0xac, 0x2a, 0x79, 0xaf, 0xf1, 0xfd, 0xe2, 0xb8, 0x8d, 0x91, 0xda, 0xf5, 0x8e, 0x86, 0xb4, 0x91, 0x3c, 0x15, 0x2a, 0x12, 0xc6, 0x98, 0x49, 0x63, 0xed, 0x50, 0xcb, 0x79, 0x0d, 0x58, 0x43, 0xf2, 0x00, 0x8e, 0x35, 0x5f, 0x25, 0x2f, 0x3c, 0xcb, 0xab, 0xd9, 0x04, 0x85, 0x20, 0x1d, 0x5e, 0x55, 0x88, 0x94, 0x64, 0x37, 0x4f, 0xd3, 0x64, 0x89, 0xbe, 0xe2, 0xcb, 0xdc, 0x96, 0xcc, 0x62, 0x99, 0x56, 0xde, 0x26, 0x8c, 0xde, 0x30, 0x18, 0x66, 0xf9, 0xd9, 0x1b, 0xf1, 0xcf, 0x32, 0xc5, 0x78, 0x48, 0x02, 0x04, 0xb2, 0x3b, 0x16, 0x3d, 0xe9, 0xa8, 0x6c, 0x81, 0x06, 0x9f, 0xf4, 0x69, 0x77, 0x7e, 0x86, 0x34, 0xa5, 0xdd, 0xb1, 0x49, 0x20, 0xe0, 0x2f, 0x17, 0x2b, 0xdc, 0x62, 0xf9, 0x93, 0x5e, 0x17, 0x51, 0x38, 0x4a, 0x82, 0x08, 0x48, 0x06, 0x24, 0x08, 0x48, 0x60, 0x92, 0xb4, 0x51, 0x13, 0x05, 0x28, 0x1a, 0xb8, 0x8d, 0x89, 0xb4, 0x28, 0x90, 0xa8, 0x64, 0x0c, 0x81, 0x44, 0xe3, 0xc6, 0x89, 0x84, 0x16, 0x0e, 0x44, 0x02, 0x40, 0x0b, 0x97, 0x2d, 0xd9, 0x48, 0x92, 0x01, 0x15, 0x64, 0x42, 0x38, 0x68, 0x1a, 0x27, 0x8e, 0x13, 0x13, 0x52, 0x00, 0x31, 0x02, 0xc4, 0x02, 0x26, 0x14, 0x42, 0x4d, 0x10, 0x28, 0x90, 0x20, 0x31, 0x32, 0x01, 0xc1, 0x60, 0x0b, 0x01, 0x6c, 0x80, 0x00, 0x6c, 0x63, 0x26, 0x50, 0x02, 0x80, 0x45, 0x4a, 0xa6, 0x40, 0x1c, 0xb2, 0x25, 0x08, 0x21, 0x62, 0x24, 0x49, 0x4e, 0x21, 0x87, 0x48, 0x1b, 0x48, 0x02, 0x1c, 0x86, 0x64, 0x04, 0xa2, 0x90, 0xda, 0xb0, 0x89, 0x14, 0x19, 0x4e, 0x49, 0x26, 0x05, 0x18, 0xc9, 0x00, 0x9a, 0xa4, 0x85, 0xdc, 0xa4, 0x48, 0x41, 0x22, 0x81, 0x02, 0x35, 0x70, 0x63, 0x26, 0x8a, 0xd4, 0x10, 0x61, 0x20, 0x44, 0x88, 0xa3, 0xa2, 0x60, 0x02, 0xa2, 0x81, 0x19, 0xb4, 0x70, 0x9c, 0x16, 0x84, 0x81, 0x02, 0x41, 0x22, 0xb5, 0x91, 0xd2, 0x08, 0x51, 0x12, 0x34, 0x41, 0xd3, 0xb8, 0x11, 0x08, 0x21, 0x32, 0x42, 0x44, 0x65, 0x00, 0x48, 0x69, 0x92, 0x46, 0x12, 0x22, 0x94, 0x61, 0xc2, 0xb6, 0x70, 0xe2, 0x98, 0x8d, 0xd3, 0x26, 0x09, 0x84, 0x22, 0x0a, 0x09, 0xb9, 0x0d, 0x4c, 0xb0, 0x64, 0x03, 0x24, 0x2e, 0x01, 0x96, 0x84, 0x99, 0x04, 0x11, 0x9b, 0xc8, 0x11, 0xa3, 0x12, 0x90, 0x0c, 0x16, 0x0a, 0x8c, 0x20, 0x2c, 0x94, 0x08, 0x46, 0x04, 0xb6, 0x0d, 0x60, 0xb0, 0x88, 0xdc, 0x04, 0x22, 0x9a, 0x46, 0x42, 0x21, 0x42, 0x4d, 0x49, 0x46, 0x29, 0x24, 0x27, 0x62, 0x13, 0x25, 0x05, 0x0a, 0x84, 0x05, 0x4a, 0x10, 0x46, 0xa4, 0x06, 0x80, 0x1c, 0x34, 0x82, 0x1a, 0xa5, 0x4c, 0x19, 0x96, 0x89, 0x24, 0x26, 0x61, 0x10, 0x46, 0x8c, 0xc1, 0x18, 0x48, 0x1c, 0x19, 0x6d, 0x04, 0x98, 0x8d, 0x8b, 0x46, 0x02, 0x02, 0x90, 0x0d, 0x5c, 0x12, 0x65, 0x4a, 0x14, 0x72, 0x02, 0xc1, 0x65, 0x1b, 0x21, 0x4d, 0x5c, 0x38, 0x6d, 0x03, 0x41, 0x8c, 0xa4, 0x26, 0x0e, 0x89, 0xa2, 0x80, 0x4a, 0x40, 0x26, 0x1c, 0x99, 0x69, 0x81, 0x12, 0x30, 0xc3, 0x40, 0x21, 0x22, 0x46, 0x61, 0x22, 0x43, 0x72, 0x02, 0x47, 0x26, 0x10, 0xa1, 0x09, 0x81, 0x22, 0x06, 0xc3, 0x22, 0x2e, 0x64, 0x32, 0x12, 0x01, 0xb2, 0x01, 0x59, 0x96, 0x45, 0x04, 0x35, 0x08, 0xc3, 0xb6, 0x8d, 0x14, 0x30, 0x6c, 0x0c, 0x95, 0x0c, 0xa3, 0x48, 0x80, 0x63, 0x40, 0x50, 0x00, 0x20, 0x26, 0xdc, 0x20, 0x24, 0x01, 0x46, 0x71, 0xe1, 0x40, 0x51, 0xc2, 0x12, 0x0d, 0xc4, 0x10, 0x40, 0x0c, 0x43, 0x24, 0x02, 0x33, 0x26, 0x02, 0xc8, 0x04, 0x1a, 0x23, 0x08, 0xe0, 0x16, 0x8a, 0x9a, 0xb0, 0x2d, 0x13, 0x40, 0x08, 0x43, 0x22, 0x32, 0x44, 0x38, 0x6d, 0xd1, 0x44, 0x90, 0xd4, 0x86, 0x09, 0x48, 0x82, 0x00, 0xe2, 0x98, 0x68, 0x12, 0xb1, 0x91, 0x09, 0x90, 0x6d, 0xc9, 0x30, 0x85, 0xc0, 0x84, 0x80, 0x9c, 0xc6, 0x4c, 0xe1, 0x10, 0x91, 0xd8, 0x96, 0x30, 0xda, 0xb6, 0x68, 0xc8, 0xc6, 0x65, 0xe3, 0x06, 0x50, 0x5c, 0x14, 0x11, 0x02, 0x44, 0x49, 0x9b, 0xa2, 0x68, 0x5a, 0x32, 0x90, 0x52, 0x80, 0x45, 0x00, 0x26, 0x30, 0x02, 0x46, 0x4c, 0xd4, 0xc8, 0x49, 0x9c, 0xb8, 0x90, 0xc4, 0x20, 0x8a, 0x0c, 0x15, 0x0d, 0x09, 0x37, 0x8e, 0x12, 0x46, 0x45, 0x08, 0x23, 0x25, 0x21, 0xa1, 0x11, 0x80, 0x90, 0x65, 0x93, 0x46, 0x72, 0xe1, 0x28, 0x6d, 0x44, 0x96, 0x51, 0x81, 0x12, 0x80, 0x04, 0x46, 0x4a, 0x8b, 0x10, 0x6d, 0x13, 0x46, 0x51, 0x53, 0x36, 0x4a, 0xe1, 0xa6, 0x45, 0xe0, 0x22, 0x01, 0x48, 0xa6, 0x00, 0x12, 0x91, 0x70, 0x23, 0xb3, 0x40, 0x02, 0xa8, 0x65, 0xdb, 0x42, 0x06, 0xe0, 0x06, 0x6a, 0xe3, 0x96, 0x61, 0x48, 0x94, 0x65, 0x43, 0x22, 0x69, 0xdc, 0x22, 0x85, 0x9b, 0x84, 0x89, 0x59, 0x18, 0x64, 0x43, 0xb0, 0x20, 0x8b, 0xa8, 0x29, 0x58, 0x30, 0x24, 0x0a, 0x17, 0x8e, 0x11, 0x41, 0x2d, 0x24, 0xb8, 0x25, 0x89, 0x14, 0x20, 0x14, 0x41, 0x91, 0x23, 0xa2, 0x24, 0x21, 0x17, 0x21, 0x11, 0xb1, 0x04, 0x03, 0x84, 0x4c, 0x0b, 0x23, 0x62, 0x50, 0x86, 0x65, 0x54, 0x22, 0x42, 0x42, 0x82, 0x80, 0x08, 0xa5, 0x51, 0xd9, 0x06, 0x31, 0x23, 0x00, 0x91, 0x10, 0xa9, 0x4d, 0xe2, 0x04, 0x66, 0x44, 0x36, 0x30, 0x8b, 0xb2, 0x49, 0x98, 0xc6, 0x70, 0x19, 0xa4, 0x8c, 0x5a, 0xa6, 0x0d, 0x63, 0x00, 0x2e, 0x9b, 0x94, 0x70, 0x8a, 0x20, 0x2d, 0x62, 0x26, 0x2c, 0x42, 0x26, 0x6c, 0x12, 0x13, 0x2e, 0xdb, 0x18, 0x08, 0x11, 0x21, 0x2e, 0x8a, 0xb0, 0x71, 0x01, 0xc1, 0x2c, 0xe3, 0x44, 0x62, 0xe3, 0x94, 0x28, 0x0b, 0x38, 0x70, 0x40, 0x14, 0x65, 0x62, 0xc0, 0x41, 0xa1, 0x06, 0x21, 0x82, 0xa2, 0x44, 0x94, 0xb0, 0x2c, 0x21, 0x10, 0x8d, 0x14, 0x96, 0x71, 0x12, 0x14, 0x05, 0x11, 0x27, 0x04, 0x12, 0x97, 0x65, 0xc4, 0x02, 0x6e, 0x58, 0xc4, 0x6c, 0xc0, 0x10, 0x70, 0x84, 0xc0, 0x45, 0x99, 0x12, 0x52, 0x80, 0xa8, 0x88, 0xd3, 0xb8, 0x28, 0x01, 0x17, 0x70, 0x03, 0xc3, 0x10, 0x88, 0x18, 0x0a, 0x53, 0x92, 0x40, 0x53, 0xc6, 0x20, 0x93, 0xc0, 0x81, 0xa4, 0x90, 0x90, 0x93, 0x92, 0x05, 0x22, 0x88, 0x08, 0xc8, 0x00, 0x64, 0xf0, 0x19, 0xcf, 0xdf, 0x37, 0x19, 0x32, 0xc9, 0xaf, 0x0a, 0x3c, 0x4a, 0x8f, 0x9c, 0xb3, 0xb4, 0x4a, 0x29, 0x5c, 0x6d, 0xd2, 0x81, 0x10, 0x3f, 0x9f, 0x4d, 0x23, 0x18, 0x65, 0xee, 0x03, 0xa7, 0xeb, 0x14, 0x99, 0xc0, 0xab, 0x6c, 0x2e, 0xad, 0x31, 0xa0, 0x15, 0x7f, 0xfd, 0x12, 0xc3, 0x0b, 0x86, 0x8d, 0x1d, 0x0f, 0x19, 0x8e, 0x2c, 0xdd, 0xc1, 0xce, 0xf2, 0x75, 0xc2, 0x3f, 0xff, 0xc3, 0xbc, 0x7d, 0x5c, 0x40, 0x50, 0x81, 0xd5, 0x92, 0xc9, 0xdc, 0x89, 0x56, 0x00, 0x04, 0x64, 0x66, 0x27, 0xa9, 0xc0, 0x43, 0xcd, 0x5d, 0xd6, 0xe6, 0xc7, 0x84, 0xa8, 0xf0, 0x02, 0xda, 0xa3, 0xf2, 0xd7, 0x27, 0xac, 0x52, 0x30, 0xb3, 0x95, 0x53, 0x34, 0x31, 0x1f, 0x06, 0xf2, 0x74, 0xba, 0x58, 0x52, 0xcf, 0xb9, 0x0b, 0xd1, 0x39, 0x3e, 0x60, 0xfe, 0xd9, 0x55, 0x72, 0xfb, 0xd9, 0x5c, 0x2d, 0x9e, 0x5f, 0x5d, 0x95, 0xe3, 0xf8, 0x25, 0x6d, 0x14, 0x70, 0x24, 0xf8, 0x15, 0x04, 0x24, 0x14, 0x15, 0xab, 0xa5, 0x33, 0xc9, 0xe0, 0xfd, 0x9c, 0xb3, 0x3d, 0x57, 0xef, 0xf4, 0xe2, 0x87, 0x21, 0x9b, 0xd4, 0x27, 0x3e, 0x6e, 0x7b, 0x23, 0x9e, 0x56, 0x7c, 0x67, 0xd2, 0x39, 0xea, 0x52, 0xb5, 0xbd, 0x6d, 0xda, 0x00, 0xc7, 0x1e, 0x0a, 0xee, 0x6d, 0x16, 0xfb, 0x9a, 0x34, 0xae, 0x8b, 0x85, 0x7b, 0x69, 0x9a, 0x98, 0xd6, 0x15, 0x26, 0x19, 0x4f, 0x09, 0xbd, 0xe0, 0x06, 0x79, 0x93, 0x2c, 0x9e, 0xaa, 0x87, 0x3c, 0xc6, 0xab, 0xca, 0x07, 0x98, 0xa9, 0xb4, 0x63, 0x90, 0x78, 0x13, 0x28, 0xdc, 0x62, 0xf3, 0x04, 0x04, 0xd5, 0x55, 0x8a, 0x91, 0xfb, 0x8b, 0xdc, 0x1d, 0x6a, 0x53, 0x16, 0xde, 0x19, 0x88, 0xe7, 0x96, 0xfc, 0xb1, 0xb5, 0x11, 0xe7, 0x91, 0x4e, 0x62, 0xc6, 0xa4, 0xb8, 0xab, 0x08, 0x7f, 0x75, 0x06, 0x45, 0x0b, 0x54, 0x63, 0x78, 0x7d, 0x0a, 0x84, 0x64, 0x96, 0xa3, 0x9d, 0x44, 0x73, 0xf6, 0x16, 0x74, 0x46, 0x35, 0x10, 0xa2, 0x9c, 0xbe, 0x5b, 0xc0, 0xe1, 0x5e, 0xc4, 0xa8, 0x24, 0xab, 0xe1, 0xc2, 0x59, 0x52, 0x16, 0xd8, 0xc9, 0xb6, 0x3e, 0x58, 0xad, 0xfb, 0xc8, 0x36, 0x65, 0x7e, 0xf1, 0x8e, 0x4f, 0x91, 0xc8, 0xe2, 0xf3, 0xa7, 0xd3, 0x28, 0xab, 0x62, 0x21, 0x96, 0x96, 0x31, 0xef, 0xa1, 0xaf, 0xe9, 0x2e, 0x36, 0xfe, 0x09, 0xeb, 0xf1, 0x8d, 0xfa, 0xfb, 0x58, 0x39, 0xa3, 0xce, 0x45, 0xe4, 0x1f, 0xdd, 0x8c, 0x24, 0xa9, 0xd7, 0x33, 0x80, 0xf5, 0xbb, 0x05, 0x11, 0x52, 0xcb, 0xbc, 0xb3, 0x09, 0x14, 0x2e, 0x0a, 0xdd, 0x44, 0xe4, 0x2f, 0x85, 0x84, 0x4e, 0x09, 0xda, 0x0b, 0x20, 0x78, 0x61, 0x2a, 0xb9, 0x67, 0x9c, 0x84, 0xe0, 0xeb, 0xbb, 0x95, 0xd0, 0x31, 0x5d, 0x83, 0x91, 0x15, 0xbf, 0x27, 0xf5, 0x1e, 0x25, 0xc9, 0xc5, 0x48, 0xa8, 0xaa, 0x8c, 0xfc, 0xea, 0x60, 0x7a, 0xcd, 0x97, 0x92, 0xab, 0x07, 0xc7, 0x9e, 0x0b, 0x54, 0xbc, 0x41, 0xdc, 0x7f, 0xf7, 0x89, 0x72, 0x12, 0xee, 0x85, 0x18, 0x86, 0x1b, 0xe0, 0x44, 0xe1, 0x4f, 0x7b, 0x75, 0x3d, 0x27, 0xf7, 0x82, 0xee, 0x38, 0xf7, 0x61, 0xe3, 0xc9, 0xa5, 0xdb, 0x59, 0xff, 0x20, 0x0d, 0x7c, 0xb8, 0xd2, 0x2c, 0xec, 0x88, 0x5d, 0xc4, 0x03, 0x08, 0x67, 0xb4, 0x72, 0x3b, 0x5c, 0xc6, 0x16, 0xab, 0x1a, 0x6b, 0x72, 0x99, 0x87, 0x80, 0xa7, 0x35, 0x5a, 0xb9, 0x91, 0x4e, 0x5c, 0x7a, 0xc6, 0x94, 0x18, 0xd2, 0xe5, 0x97, 0x7c, 0xd5, 0x91, 0x5d, 0x57, 0x56, 0xe9, 0xff, 0x5a, 0x64, 0xf9, 0xc8, 0xff, 0x2a, 0x5a, 0xba, 0xce, 0x0d, 0xcf, 0x67, 0x09, 0xb1, 0x2d, 0x63, 0xc5, 0x72, 0x78, 0xc7, 0x4f, 0xc1, 0xc0, 0x23, 0x42, 0xaf, 0xf2, 0xb8, 0x2f, 0x79, 0xb7, 0xf7, 0x5d, 0xa5, 0xba, 0xd5, 0x0f, 0xa8, 0x9b, 0xf2, 0xaf, 0x5d, 0x72, 0x92, 0x86, 0xce, 0x10, 0x52, 0xd3, 0xdd, 0x15, 0x15, 0x65, 0xa8, 0x38, 0xc2, 0x98, 0x27, 0x47, 0x4e, 0xb1, 0xde, 0x05, 0x8b, 0xd9, 0x36, 0xd7, 0x0f, 0xf5, 0x33, 0x6e, 0x4c, 0x9c, 0x49, 0x7d, 0x8e, 0x07, 0x79, 0x77, 0x14, 0x8a, 0xea, 0x3b, 0x86, 0xc4, 0xaf, 0xf9, 0x4c, 0x8f, 0x43, 0x26, 0xbf, 0xa4, 0x68, 0xf4, 0xb3, 0xe7, 0xd2, 0x03, 0xc4, 0x85, 0x1c, 0xd5, 0x0a, 0x18, 0x55, 0x51, 0xfe, 0xb1, 0x5b, 0x8e, 0x79, 0xed, 0x07, 0x87, 0x7d, 0xba, 0xd4, 0x09, 0x98, 0x93, 0xcb, 0xa9, 0x4f, 0x31, 0xce, 0xe2, 0xab, 0x3a, 0xf2, 0x6d, 0x3a, 0xeb, 0x4f, 0x2c, 0x1a, 0x6b, 0xf2, 0xff, 0x81, 0xfa, 0xf4, 0x34, 0xbe, 0xb5, 0x4e, 0x1a, 0xea, 0xf2, 0x10, 0x7b, 0x3e, 0x96, 0xcf, 0x67, 0x37, 0xd8, 0xae, 0xf0, 0x3d, 0x03, 0xa8, 0xe6, 0x93, 0x1d, 0x59, 0xbc, 0x1a, 0x06, 0xb4, 0x1c, 0x0d, 0x68, 0xf2, 0xbe, 0x27, 0x58, 0x1a, 0x66, 0x92, 0xca, 0x37, 0x63, 0x67, 0x2a, 0x59, 0x62, 0xbd, 0x40, 0xd5, 0xe9, 0xd9, 0x4a, 0x49, 0x69, 0x8c, 0x4c, 0xf4, 0x65, 0x85, 0x34, 0xcc, 0x37, 0xd0, 0x5e, 0x3e, 0x65, 0x8a, 0x73, 0x6b, 0x32, 0xd2, 0xfa, 0x6c, 0x54, 0x94, 0xb7, 0x20, 0x75, 0x6e, 0x4a, 0xc9, 0xf8, 0x72, 0xc8, 0xdc, 0xda, 0x09, 0xca, 0xe3, 0x94, 0x9b, 0xf8, 0xeb, 0xe8, 0x32, 0xbe, 0xbb, 0x41, 0x68, 0xb6, 0x01, 0x8d, 0xe9, 0x9f, 0xd3, 0x7f, 0xfd, 0x91, 0xe2, 0x2b, 0xc0, 0x4e, 0xf9, 0x42, 0x5b, 0xa2, 0xec, 0xc8, 0x35, 0x0f, 0x36, 0xd9, 0xd1, 0x88, 0x65, 0xa2, 0x2a, 0xea, 0x50, 0x99, 0x19, 0x50, 0x31, 0x24, 0x10, 0x7b, 0x56, 0x67, 0xa4, 0xa5, 0xca, 0xe3, 0xa5, 0xc9, 0x77, 0xb4, 0xd1, 0x8a, 0xdc, 0xa4, 0xff, 0xca, 0xee, 0xd8, 0x58, 0x3e, 0x6d, 0xa5, 0xd4, 0xb3, 0x39, 0x15, 0xa2, 0xcb, 0x02, 0x9c, 0xfe, 0x93, 0x66, 0x18, 0xd8, 0xd2, 0xce, 0xb2, 0x8d, 0x4d, 0x28, 0x62, 0xc4, 0x7b, 0x39, 0xe2, 0x2a, 0x02, 0x6e, 0x38, 0x59, 0xcc, 0x35, 0xda, 0x99, 0xb2, 0xc1, 0x93, 0x24, 0xb6, 0x63, 0xa8, 0xfe, 0x37, 0x91, 0x32, 0x78, 0x11, 0xf9, 0x95, 0x72, 0x2d, 0xd1, 0x51, 0x40, 0x13, 0x90, 0xfa, 0xa7, 0x3d, 0x5d, 0xfa, 0xce, 0xc1, 0x3d, 0xd4, 0xab, 0xb7, 0x4b, 0x8c, 0xd1, 0xd9, 0x45, 0xc6, 0x7e, 0x0c, 0xc6, 0xbc, 0xdd, 0x11, 0xfa, 0x52, 0x83, 0x59, 0x2d, 0xa7, 0xa8, 0xae, 0x3f, 0xb8, 0x58, 0xa2, 0x84, 0x14, 0x05, 0x77, 0xf9, 0xb9, 0xfe, 0x05, 0xd1, 0x4a, 0xf9, 0xe8, 0x7d, 0x1e, 0xb8, 0xb4, 0x39, 0xfc, 0x80, 0x1d, 0xb2, 0x38, 0x8d, 0xc4, 0x63, 0xc0, 0xe9, 0x15, 0x5f, 0xfe, 0xf6, 0x81, 0xb2, 0x3d, 0xe0, 0x13, 0xec, 0xd0, 0x75, 0x1d, 0x03, 0xb8, 0x6b, 0xdc, 0x13, 0xb6, 0x09, 0xb1, 0x82, 0xe4, 0x02, 0x12, 0x18, 0xcb, 0x1e, 0x2d, 0xce, 0xee, 0xf2, 0xc4, 0xa4, 0x9a, 0x1b, 0xa6, 0x51, 0xc3, 0xfd, 0x73, 0xc1, 0xc5, 0x85, 0xe7, 0xbb, 0x44, 0xe7, 0x61, 0x9e, 0x00, 0x03, 0x0a, 0x18, 0xe4, 0x62, 0x86, 0x45, 0xd8, 0xfa, 0x5b, 0x71, 0x46, 0x86, 0x0e, 0x3a, 0x89, 0x6d, 0xb2, 0xd7, 0x0e, 0xd7, 0x65, 0x3e, 0xcc, 0x16, 0xe5, 0x9c, 0x2d, 0xf2, 0x0e, 0x44, 0x64, 0x14, 0xfd, 0xa9, 0x2f, 0xb6, 0xe8, 0x78, 0xa2, 0x54, 0xa3, 0x45, 0x5e, 0xb0, 0x14, 0x02, 0xf7, 0xa1, 0xe0, 0x16, 0x58, 0xd9, 0xc3, 0x58, 0x5a, 0xe6, 0x72, 0xa7, 0x0a, 0x9f, 0x33, 0xf8, 0xd5, 0x18, 0x54, 0x1e, 0x80, 0x54, 0x1e, 0x51, 0x69, 0x53, 0x60, 0x57, 0xf0, 0xf9, 0xc6, 0x97, 0x4b, 0x5b, 0x98, 0xe6, 0x1a, 0xc1, 0xb4, 0x61, 0xed, 0x3d, 0xc7, 0xe8, 0x14, 0xd6, 0x92, 0x49, 0x7c, 0x46, 0x1e, 0x3a, 0x20, 0xb6, 0x20, 0xb3, 0x25, 0xe0, 0xf8, 0x39, 0xea, 0xc9, 0x7d, 0xcb, 0x38, 0x98, 0x03, 0x95, 0x9a, 0x99, 0x69, 0xae, 0xd4, 0x0c, 0x1e, 0x50, 0x34, 0x6d, 0x85, 0x6a, 0x33, 0x2f, 0x8c, 0x03, 0x6a, 0xa4, 0x1a, 0xfd, 0xac, 0x8a, 0x34, 0x08, 0xe9, 0x88, 0xb6, 0xa0, 0xb9, 0x96, 0xa7, 0x41, 0x37, 0x7f, 0xe7, 0xc2, 0xd1, 0xaf, 0xe5, 0x60, 0x68, 0x06, 0xfe, 0x70, 0x81, 0x80, 0xb4, 0xfe, 0xf7, 0x6d, 0x88, 0xec, 0xc6, 0x90, 0x38, 0x04, 0x34, 0x24, 0x1b, 0xb8, 0x57, 0x86, 0x40, 0xd2, 0x19, 0xec, 0xa1, 0x36, 0x11, 0xff, 0x22, 0x34, 0x8c, 0x31, 0x3f, 0x63, 0xa1, 0x4f, 0xce, 0x67, 0x73, 0x5c, 0x78, 0x5d, 0x85, 0xc8, 0xd8, 0x40, 0x5a, 0x40, 0xdf, 0x88, 0x3d, 0xf3, 0x6d, 0xf4, 0x9b, 0xb6, 0x29, 0xbf, 0x07, 0xdd, 0xd9, 0x51, 0x27, 0xa6, 0x97, 0xb2, 0x6f, 0x61, 0xef, 0x1c, 0xa9, 0x01, 0x52, 0x2b, 0xd0, 0x12, 0xe4, 0x40, 0xa6, 0xea, 0x25, 0x80, 0xba, 0x80, 0x61, 0x87, 0xa5, 0x8d, 0x7e, 0x71, 0x09, 0x68, 0xfb, 0xc6, 0x08, 0x2f, 0x98, 0x2c, 0xe7, 0xab, 0x30, 0xb4, 0xde, 0x39, 0xb6, 0x71, 0xff, 0x32, 0x90, 0x61, 0x65, 0xf9, 0xc4, 0x16, 0x7e, 0xda, 0xc4, 0x05, 0x77, 0x0b, 0xf1, 0xf9, 0xe0, 0xc0, 0x7d, 0x14, 0x57, 0x6f, 0x48, 0xba, 0xea, 0xe0, 0xc5, 0x93, 0x60, 0x14, 0xe3, 0xf8, 0x6a, 0x67, 0xb2, 0xdc, 0x56, 0xe8, 0x37, 0x7f, 0x59, 0x63, 0x5c, 0x77, 0xd2, 0xe3, 0xa7, 0x73, 0x53, 0x9c, 0x8d, 0xf4, 0xac, 0xe9, 0x07, 0x8a, 0x1c, 0xbe, 0xa7, 0x4d, 0x1f, 0x60, 0x36, 0x9f, 0x33, 0x9d, 0xf4, 0x2d, 0x4f, 0x61, 0xdd, 0x33, 0x40, 0x1f, 0x6e, 0x10, 0x9f, 0xf7, 0x1f, 0xf0, 0x95, 0x89, 0x62, 0x8c, 0x04, 0xf7, 0x64, 0xe0, 0x66, 0xd0, 0x4b, 0x4a, 0x79, 0x96, 0x70, 0x56, 0xdb, 0x07, 0xda, 0xaf, 0x00, 0xd1, 0x54, 0xec, 0x07, 0xac, 0x09, 0x25, 0x91, 0x46, 0xd1, 0x4f, 0xbf, 0x50, 0xe9, 0xe6, 0x54, 0x73, 0x91, 0xc7, 0xfb, 0x67, 0x33, 0xeb, 0x01, 0x1b, 0xdc, 0x45, 0x5a, 0xdc, 0xa3, 0x96, 0x35, 0x6c, 0x71, 0x9b, 0xa6, 0xe7, 0x2b, 0x65, 0x95, 0x2d, 0xae, 0x81, 0x0a, 0x28, 0x31, 0xf0, 0x2a, 0x9e, 0x01, 0xe2, 0x83, 0x2f, 0xe0, 0xa4, 0x65, 0xca, 0x92, 0x4a, 0x0f, 0x32, 0x45, 0xb5, 0xe6, 0x19, 0x24, 0x44, 0x2b, 0x2b, 0xea, 0x64, 0x46, 0xb2, 0x49, 0xd0, 0x2f, 0xe2, 0x64, 0x0d, 0x1f, 0xee, 0xe4, 0x29, 0x04, 0x99, 0x80, 0x8b, 0x7c, 0x7a, 0x3a, 0x4c, 0xd4, 0x18, 0xd4, 0xf7, 0x3b, 0x0c, 0x44, 0x39, 0x3d, 0x0f, 0x10, 0xd4, 0x1f, 0x47, 0x7f, 0xb1, 0xdf, 0xd2, 0xc1, 0xd7, 0x1d, 0x1f, 0xf7, 0x29, 0x36, 0x54, 0x4b, 0x8e, 0x55, 0x9b, 0xcb, 0x08, 0xf5, 0x31, 0x0f, 0xd4, 0x0c, 0x5e, 0x18, 0x59, 0x7e, 0xea, 0xef, 0x5a, 0xd2, 0x0d, 0xc6, 0x94, 0x5d, 0x83, 0xbd, 0x55, 0xa9, 0x2f, 0xfe, 0x85, 0x82, 0xd9, 0xa9, 0x91, 0x40, 0xf6, 0xcc, 0xf9, 0x88, 0xba, 0x72, 0x09, 0x36, 0x9f, 0xa1, 0xc5, 0x7c, 0xea, 0x93, 0xd4, 0xae, 0x48, 0xaa, 0x2e, 0x91, 0x93, 0x2f, 0x1b, 0x66, 0x3e, 0x87, 0x0e, 0xdd, 0xa0, 0x1e, 0x19, 0x8d, 0x25, 0xdf, 0xbf, 0x39, 0x82, 0xdf, 0x7a, 0x7c, 0x7c, 0x95, 0xb2, 0xbd, 0x27, 0x2b, 0x3d, 0x76, 0xef, 0x05, 0x38, 0xfc, 0x8a, 0x38, 0x46, 0x6d, 0xd5, 0xaa, 0x39, 0x6b, 0xa9, 0xca, 0xf5, 0x9f, 0xfd, 0x81, 0xc2, 0x4f, 0xa5, 0x8c, 0x12, 0x90, 0xa3, 0x03, 0xe6, 0xfd, 0x79, 0x6a, 0x48, 0x69, 0x6c, 0xc3, 0x78, 0x30, 0x9d, 0x79, 0xa6, 0x81, 0xa7, 0xf4, 0xe9, 0xcf, 0x4e, 0x9d, 0x58, 0x01, 0x10, 0xed, 0x2c, 0x27, 0x2e, 0x5c, 0xaa, 0xc8, 0xd0, 0x57, 0xef, 0x05, 0xa5, 0xe7, 0x9f, 0x8d, 0x07, 0xa7, 0xd7, 0x48, 0x05, 0x7b, 0x70, 0x17, 0xac, 0xe3, 0xd4, 0x4a, 0x09, 0x92, 0xc4, 0x8e, 0x84, 0x03, 0x74, 0x5a, 0x61, 0x61, 0x0e, 0x1a, 0x0c, 0x1f, 0x8c, 0x4e, 0xd9, 0x6c, 0x96, 0x42, 0xac, 0x93, 0x0b, 0xd4, 0x14, 0x6c, 0xd8, 0x27, 0x0e, 0x9b, 0xb9, 0x77, 0x0d, 0xd5, 0xdd, 0x82, 0x08, 0xf5, 0x89, 0xdb, 0x44, 0x86, 0x8d, 0xb2, 0x2c, 0xa8, 0x06, 0x5f, 0xbc, 0x4c, 0x73, 0x27, 0xfe, 0x32, 0x26, 0x3c, 0x33, 0x83, 0xda, 0x18, 0xc9 }; #endif /* !WOLFSSH_NO_MLDSA */ #ifndef WOLFSSH_NO_ECDSA /* P-256 DER with the OID last byte changed 0x07 -> 0x01 (secp192r1). * Forces wc_EccPrivateKeyDecode to fail or to return an unsupported curve id, * exercising the wc_ecc_free cleanup in both the default: and else paths. */ static const byte unitTestEccUnsupportedCurveKey[] = { 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x61, 0x09, 0x99, 0x0B, 0x79, 0xD2, 0x5F, 0x28, 0x5A, 0x0F, 0x5D, 0x15, 0xCC, 0xA1, 0x56, 0x54, 0xF9, 0x2B, 0x39, 0x87, 0x21, 0x2D, 0xA7, 0x7D, 0x85, 0x7B, 0xB8, 0x7F, 0x38, 0xC6, 0x6D, 0xD5, 0xA0, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x01, /* 0x07 (secp256r1) changed to 0x01 (secp192r1) */ 0xA1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x81, 0x13, 0xFF, 0xA4, 0x2B, 0xB7, 0x9C, 0x45, 0x74, 0x7A, 0x83, 0x4C, 0x61, 0xF3, 0x3F, 0xAD, 0x26, 0xCF, 0x22, 0xCD, 0xA9, 0xA3, 0xBC, 0xA5, 0x61, 0xB4, 0x7C, 0xE6, 0x62, 0xD4, 0xC2, 0xF7, 0x55, 0x43, 0x9A, 0x31, 0xFB, 0x80, 0x11, 0x20, 0xB5, 0x12, 0x4B, 0x24, 0xF5, 0x78, 0xD7, 0xFD, 0x22, 0xEF, 0x46, 0x35, 0xF0, 0x05, 0x58, 0x6B, 0x5F, 0x63, 0xC8, 0xDA, 0x1B, 0xC4, 0xF5, 0x69 }; #endif /* WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_RSA /* wolfSSH_RsaVerify unit test * * Verifies that wolfSSH_RsaVerify returns WS_RSA_E when given a signature * whose decoded digest is the correct size but contains wrong content. * This makes the `compare = ConstantCompare(...)` term in wolfSSH_RsaVerify * load-bearing: deleting it from the condition would silently pass this test. */ static int test_RsaVerify_BadDigest(void) { int result = 0; int ret; RsaKey key; WC_RNG rng; word32 idx = 0; byte data[32]; byte digest[WC_SHA256_DIGEST_SIZE]; byte encDigest[MAX_ENCODED_SIG_SZ]; int encDigestSz; byte badEncDigest[MAX_ENCODED_SIG_SZ]; byte sig[256]; /* 2048-bit RSA produces a 256-byte signature */ int sigSz; WMEMSET(data, 0x42, sizeof(data)); if (wc_InitRng(&rng) != 0) { printf("RsaVerify_BadDigest: wc_InitRng failed\n"); return -500; } if (wc_InitRsaKey(&key, NULL) != 0) { printf("RsaVerify_BadDigest: wc_InitRsaKey failed\n"); wc_FreeRng(&rng); return -501; } ret = wc_RsaPrivateKeyDecode(unitTestRsaPrivKey, &idx, &key, unitTestRsaPrivKeySz); if (ret != 0) { result = -502; goto done; } /* Hash the payload */ ret = wc_Hash(WC_HASH_TYPE_SHA256, data, sizeof(data), digest, WC_SHA256_DIGEST_SIZE); if (ret != 0) { result = -503; goto done; } /* Encode as PKCS#1 v1.5 DigestInfo */ encDigestSz = wc_EncodeSignature(encDigest, digest, WC_SHA256_DIGEST_SIZE, wc_HashGetOID(WC_HASH_TYPE_SHA256)); if (encDigestSz <= 0) { result = -504; goto done; } /* Sign */ sigSz = wc_RsaSSL_Sign(encDigest, (word32)encDigestSz, sig, sizeof(sig), &key, &rng); if (sigSz <= 0) { result = -505; goto done; } /* Positive case: correct sig + correct encDigest must succeed */ ret = wolfSSH_TestRsaVerify(sig, (word32)sigSz, encDigest, (word32)encDigestSz, &key, NULL); if (ret != WS_SUCCESS) { result = -506; goto done; } /* Negative case: correct sig but tampered encDigest (same size, * last byte of the SHA-256 hash flipped) must return WS_RSA_E. * This is the scenario that deleting `compare` from the condition * inside wolfSSH_RsaVerify would silently pass. */ WMEMCPY(badEncDigest, encDigest, encDigestSz); badEncDigest[encDigestSz - 1] ^= 0xFF; ret = wolfSSH_TestRsaVerify(sig, (word32)sigSz, badEncDigest, (word32)encDigestSz, &key, NULL); if (ret != WS_RSA_E) { result = -507; goto done; } done: wc_FreeRng(&rng); wc_FreeRsaKey(&key); return result; } #endif /* !WOLFSSH_NO_RSA */ #if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_VERIFY) && \ defined(WOLFSSL_ED25519_STREAMING_VERIFY) /* Locally-generated Ed25519 keypair for the DoUserAuthRequestEd25519 test. * 32-byte private seed followed by the 32-byte raw public key. Created with * ssh-keygen and decoded from the OpenSSH private key format so the test is * deterministic and does not depend on an RNG. */ static const byte unitTestEd25519Priv[32] = { 0x05, 0xf5, 0x9c, 0x02, 0x55, 0x93, 0x32, 0x93, 0xb9, 0xc2, 0x2e, 0xa7, 0x20, 0x05, 0x33, 0x0c, 0x40, 0xcd, 0xfa, 0xff, 0x73, 0xe4, 0x4a, 0xe1, 0x50, 0x2a, 0x4b, 0x37, 0x20, 0x66, 0xc5, 0x56 }; static const byte unitTestEd25519Pub[32] = { 0x33, 0x76, 0xaf, 0x20, 0x97, 0xce, 0x38, 0xdf, 0x5a, 0x76, 0x62, 0xfc, 0xb2, 0x87, 0x6e, 0x9d, 0xd3, 0x9e, 0x85, 0x87, 0xf3, 0x0e, 0x72, 0x6c, 0x1e, 0xc0, 0x01, 0xe2, 0x81, 0x96, 0xb8, 0x49 }; /* Write a 32-bit big-endian length prefix at out. */ static void Ed25519Test_PutLen(byte* out, word32 v) { out[0] = (byte)(v >> 24); out[1] = (byte)(v >> 16); out[2] = (byte)(v >> 8); out[3] = (byte)(v); } static int test_ParseEd25519PubKey(void) { static const char keyTypeName[] = "ssh-ed25519"; const word32 keyTypeNameSz = (word32)(sizeof(keyTypeName) - 1); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte blob[64]; word32 blobSz, off; int ret; int failures = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return 1; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return 1; } /* string "ssh-ed25519" || string pubkey */ off = 0; Ed25519Test_PutLen(blob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(blob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; Ed25519Test_PutLen(blob + off, (word32)sizeof(unitTestEd25519Pub)); off += UINT32_SZ; WMEMCPY(blob + off, unitTestEd25519Pub, sizeof(unitTestEd25519Pub)); off += (word32)sizeof(unitTestEd25519Pub); blobSz = off; /* valid blob */ ret = wolfSSH_TestParseEd25519PubKey(ssh, blob, blobSz); if (ret != WS_SUCCESS) { fprintf(stderr, "\t\"valid\" FAIL: got %d, expected %d\n", ret, WS_SUCCESS); failures++; } /* truncated blob: fails after the key is initialized */ ret = wolfSSH_TestParseEd25519PubKey(ssh, blob, blobSz - 4); if (ret != WS_BUFFER_E) { fprintf(stderr, "\t\"truncated\" FAIL: got %d, expected %d\n", ret, WS_BUFFER_E); failures++; } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return failures; } /* DoUserAuthRequestEd25519 unit test * * Drives DoUserAuthRequestEd25519 directly with a fully-formed Ed25519 * USERAUTH_REQUEST and asserts (1) a correct signature returns WS_SUCCESS * and (2) a tampered signature returns either WS_ED25519_E or * WS_CRYPTO_FAILED. This is the test that makes the * `status ? WS_SUCCESS : WS_ED25519_E` style mapping load-bearing -- without * the negative case any mutation that hard-codes WS_SUCCESS or inverts the * status would silently survive. * * The Ed25519 verifier in wolfSSL has two failure paths reachable from this * function: wc_ed25519_verify_msg_final returns non-zero (WS_CRYPTO_FAILED) * or returns zero with status=0 (WS_ED25519_E). Different wolfSSL versions * have routed bad signatures through either branch; we accept either failure * code so the test stays portable while still killing the most dangerous * mutation -- turning either error into WS_SUCCESS. */ static int test_DoUserAuthRequestEd25519(void) { static const char keyTypeName[] = "ssh-ed25519"; static const char username[] = "wolfssh"; static const char serviceName[] = "ssh-connection"; static const char authName[] = "publickey"; const word32 keyTypeNameSz = (word32)(sizeof(keyTypeName) - 1); const word32 usernameSz = (word32)(sizeof(username) - 1); const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); const word32 authNameSz = (word32)(sizeof(authName) - 1); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; ed25519_key signingKey; int signingKeyInit = 0; WS_UserAuthData authData; byte pubKeyBlob[64]; byte sigBlob[128]; byte badSigBlob[128]; byte dataToSign[256]; byte checkData[512]; byte sig[ED25519_SIG_SIZE]; word32 pubKeyBlobSz = 0; word32 sigBlobSz = 0; word32 dataToSignSz = 0; word32 checkDataSz = 0; word32 sigSz = sizeof(sig); word32 off; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -600; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -601; goto done; } /* Stub a session id so the verify hash has something to absorb. */ ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); /* Load the embedded Ed25519 keypair for signing. */ if (wc_ed25519_init(&signingKey) != 0) { result = -602; goto done; } signingKeyInit = 1; if (wc_ed25519_import_private_key(unitTestEd25519Priv, (word32)sizeof(unitTestEd25519Priv), unitTestEd25519Pub, (word32)sizeof(unitTestEd25519Pub), &signingKey) != 0) { result = -603; goto done; } /* Build the SSH public key blob: string "ssh-ed25519" || string pubkey */ off = 0; Ed25519Test_PutLen(pubKeyBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; Ed25519Test_PutLen(pubKeyBlob + off, (word32)sizeof(unitTestEd25519Pub)); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, unitTestEd25519Pub, sizeof(unitTestEd25519Pub)); off += (word32)sizeof(unitTestEd25519Pub); pubKeyBlobSz = off; /* Build the dataToSign region the same way the wire packet would lay it * out: username || service || authmethod || hasSig=1 || pkAlgo || pkBlob. * DoUserAuthRequestEd25519 hashes exactly this region. */ off = 0; Ed25519Test_PutLen(dataToSign + off, usernameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, username, usernameSz); off += usernameSz; Ed25519Test_PutLen(dataToSign + off, serviceNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, serviceName, serviceNameSz); off += serviceNameSz; Ed25519Test_PutLen(dataToSign + off, authNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, authName, authNameSz); off += authNameSz; dataToSign[off++] = 1; /* hasSignature */ Ed25519Test_PutLen(dataToSign + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; Ed25519Test_PutLen(dataToSign + off, pubKeyBlobSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, pubKeyBlob, pubKeyBlobSz); off += pubKeyBlobSz; dataToSignSz = off; /* Build the bytes that get signed: sessionIdSz || sessionId || * MSGID_USERAUTH_REQUEST || dataToSign. Mirrors BuildUserAuthRequestEd25519 * on the client side. */ off = 0; Ed25519Test_PutLen(checkData + off, ssh->sessionIdSz); off += UINT32_SZ; WMEMCPY(checkData + off, ssh->sessionId, ssh->sessionIdSz); off += ssh->sessionIdSz; checkData[off++] = MSGID_USERAUTH_REQUEST; WMEMCPY(checkData + off, dataToSign, dataToSignSz); off += dataToSignSz; checkDataSz = off; if (wc_ed25519_sign_msg(checkData, checkDataSz, sig, &sigSz, &signingKey) != 0) { result = -604; goto done; } /* Build the SSH signature blob: string "ssh-ed25519" || string sig */ off = 0; Ed25519Test_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; Ed25519Test_PutLen(sigBlob + off, sigSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, sig, sigSz); off += sigSz; sigBlobSz = off; /* Populate authData the way DoUserAuthRequest/DoUserAuthRequestPublicKey * would before dispatching to DoUserAuthRequestEd25519. */ WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.username = (const byte*)username; authData.usernameSz = usernameSz; authData.serviceName = (const byte*)serviceName; authData.serviceNameSz = serviceNameSz; authData.authName = (const byte*)authName; authData.authNameSz = authNameSz; authData.sf.publicKey.dataToSign = dataToSign; authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; authData.sf.publicKey.publicKey = pubKeyBlob; authData.sf.publicKey.publicKeySz = pubKeyBlobSz; authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.signature = sigBlob; authData.sf.publicKey.signatureSz = sigBlobSz; /* Positive case: untouched signature must verify. */ ret = wolfSSH_TestDoUserAuthRequestEd25519(ssh, &authData); if (ret != WS_SUCCESS) { printf("DoUserAuthRequestEd25519 positive: ret=%d (expected %d)\n", ret, WS_SUCCESS); result = -605; goto done; } /* Negative case: flip a byte inside the raw signature (skip past the * 4 + keyTypeNameSz + 4 header so we land in the actual signature * material). Must NOT return WS_SUCCESS. */ WMEMCPY(badSigBlob, sigBlob, sigBlobSz); badSigBlob[UINT32_SZ + keyTypeNameSz + UINT32_SZ + 10] ^= 0xFF; authData.sf.publicKey.signature = badSigBlob; ret = wolfSSH_TestDoUserAuthRequestEd25519(ssh, &authData); if (ret != WS_ED25519_E && ret != WS_CRYPTO_FAILED) { printf("DoUserAuthRequestEd25519 tampered: ret=%d\n", ret); result = -606; goto done; } done: if (signingKeyInit) wc_ed25519_free(&signingKey); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #endif /* Ed25519 verify test guards */ #ifndef WOLFSSH_NO_MLDSA /* Write 32-bit big-endian length into out. */ static void MlDsaTest_PutLen(byte* out, word32 v) { out[0] = (byte)(v >> 24); out[1] = (byte)(v >> 16); out[2] = (byte)(v >> 8); out[3] = (byte)(v); } static int test_DoUserAuthRequestMlDsa_Params(const char* keyTypeName, byte level) { static const char username[] = "wolfssh"; static const char serviceName[] = "ssh-connection"; static const char authName[] = "publickey"; const word32 keyTypeNameSz = (word32)(WSTRLEN(keyTypeName)); const word32 usernameSz = (word32)(sizeof(username) - 1); const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); const word32 authNameSz = (word32)(sizeof(authName) - 1); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; MlDsaKey signingKey; int signingKeyInit = 0; WC_RNG rng; int rngInit = 0; WS_UserAuthData authData; byte* pubKeyBlob = NULL; byte* sigBlob = NULL; byte* badSigBlob = NULL; byte* dataToSign = NULL; byte* checkData = NULL; byte* sig = NULL; byte* pubRaw = NULL; word32 pubKeyBlobSz = 0; word32 sigBlobSz = 0; word32 dataToSignSz = 0; word32 checkDataSz = 0; int sigSzInt; word32 sigSz = 0; int pubRawSzInt; word32 pubRawSz = 0; word32 off; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -700; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -701; goto done; } /* Stub a session id so the verify hash has something to absorb. */ ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); if (wc_InitRng(&rng) != 0) { result = -702; goto done; } rngInit = 1; if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { result = -703; goto done; } signingKeyInit = 1; if (wc_MlDsaKey_SetParams(&signingKey, level) != 0) { result = -704; goto done; } if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { result = -705; goto done; } sigSzInt = wc_MlDsaKey_SigSize(&signingKey); if (sigSzInt < 0) { result = -706; goto done; } sigSz = (word32)sigSzInt; sig = (byte*)WMALLOC(sigSz, NULL, 0); if (sig == NULL) { result = -718; goto done; } /* Get raw public key to build pubKeyBlob */ pubRawSzInt = wc_MlDsaKey_PubSize(&signingKey); if (pubRawSzInt < 0) { result = -707; goto done; } pubRawSz = (word32)pubRawSzInt; pubRaw = (byte*)WMALLOC(pubRawSz, NULL, 0); if (pubRaw == NULL) { result = -717; goto done; } if (wc_MlDsaKey_ExportPubRaw(&signingKey, pubRaw, &pubRawSz) != 0) { result = -708; goto done; } pubKeyBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + pubRawSz, NULL, 0); if (pubKeyBlob == NULL) { result = -709; goto done; } /* Build the SSH public key blob: string keyTypeName || string pubkey */ off = 0; MlDsaTest_PutLen(pubKeyBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(pubKeyBlob + off, pubRawSz); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, pubRaw, pubRawSz); off += pubRawSz; pubKeyBlobSz = off; /* Build dataToSign: user || svc || auth || hasSig || algo || blob. */ dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + 1 + keyTypeNameSz + pubKeyBlobSz; dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); if (dataToSign == NULL) { result = -710; goto done; } off = 0; MlDsaTest_PutLen(dataToSign + off, usernameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, username, usernameSz); off += usernameSz; MlDsaTest_PutLen(dataToSign + off, serviceNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, serviceName, serviceNameSz); off += serviceNameSz; MlDsaTest_PutLen(dataToSign + off, authNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, authName, authNameSz); off += authNameSz; dataToSign[off++] = 1; /* hasSignature */ MlDsaTest_PutLen(dataToSign + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(dataToSign + off, pubKeyBlobSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, pubKeyBlob, pubKeyBlobSz); off += pubKeyBlobSz; /* Build checkData: session ID || msg ID || dataToSign. */ checkDataSz = UINT32_SZ + ssh->sessionIdSz + MSG_ID_SZ + dataToSignSz; checkData = (byte*)WMALLOC(checkDataSz, NULL, 0); if (checkData == NULL) { result = -711; goto done; } off = 0; MlDsaTest_PutLen(checkData + off, ssh->sessionIdSz); off += UINT32_SZ; WMEMCPY(checkData + off, ssh->sessionId, ssh->sessionIdSz); off += ssh->sessionIdSz; checkData[off++] = MSGID_USERAUTH_REQUEST; WMEMCPY(checkData + off, dataToSign, dataToSignSz); if (wc_MlDsaKey_SignCtx(&signingKey, NULL, 0, sig, &sigSz, checkData, checkDataSz, &rng) != 0) { result = -712; goto done; } /* Build the SSH signature blob: string keyTypeName || string sig */ sigBlobSz = UINT32_SZ * 2 + keyTypeNameSz + sigSz; sigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); if (sigBlob == NULL) { result = -713; goto done; } off = 0; MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(sigBlob + off, sigSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, sig, sigSz); off += sigSz; sigBlobSz = off; /* Populate authData */ WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.username = (const byte*)username; authData.usernameSz = usernameSz; authData.serviceName = (const byte*)serviceName; authData.serviceNameSz = serviceNameSz; authData.authName = (const byte*)authName; authData.authNameSz = authNameSz; authData.sf.publicKey.dataToSign = dataToSign; authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; authData.sf.publicKey.publicKey = pubKeyBlob; authData.sf.publicKey.publicKeySz = pubKeyBlobSz; authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.signature = sigBlob; authData.sf.publicKey.signatureSz = sigBlobSz; /* Positive case: untouched signature must verify. */ ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); if (ret != WS_SUCCESS) { printf("DoUserAuthRequestMlDsa positive (%s): ret=%d (expected %d)\n", keyTypeName, ret, WS_SUCCESS); result = -714; goto done; } /* Negative case: flip a byte inside the raw signature */ badSigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); if (badSigBlob == NULL) { result = -715; goto done; } WMEMCPY(badSigBlob, sigBlob, sigBlobSz); badSigBlob[UINT32_SZ + keyTypeNameSz + UINT32_SZ + 10] ^= 0xFF; authData.sf.publicKey.signature = badSigBlob; ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); if (ret != WS_MLDSA_E && ret != WS_CRYPTO_FAILED) { printf("DoUserAuthRequestMlDsa tampered (%s): ret=%d (expected %d)\n", keyTypeName, ret, WS_MLDSA_E); result = -716; goto done; } done: if (signingKeyInit) wc_MlDsaKey_Free(&signingKey); if (rngInit) wc_FreeRng(&rng); if (pubKeyBlob != NULL) WFREE(pubKeyBlob, NULL, 0); if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); if (badSigBlob != NULL) WFREE(badSigBlob, NULL, 0); if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); if (checkData != NULL) WFREE(checkData, NULL, 0); if (sig != NULL) WFREE(sig, NULL, 0); if (pubRaw != NULL) WFREE(pubRaw, NULL, 0); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } /* unknown publicKeyType must be rejected at the boundary */ static int test_DoUserAuthRequestMlDsa_BadAlgo(void) { static const char badAlgo[] = "not-an-mldsa-key"; WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WS_UserAuthData authData; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -800; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -801; goto done; } WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.sf.publicKey.publicKeyType = (const byte*)badAlgo; authData.sf.publicKey.publicKeyTypeSz = (word32)(sizeof(badAlgo) - 1); /* pubKeyBlobSz=0: the bad key type is rejected before dataToSign is sized, * so the value is irrelevant for this code path. */ ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, 0); if (ret != WS_INVALID_ALGO_ID) { printf("DoUserAuthRequestMlDsa bad-algo: ret=%d expected %d\n", ret, WS_INVALID_ALGO_ID); result = -802; } done: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #ifdef WOLFSSH_CERTS /* Confirm the cert branch is entered, ParseCertChainVerify intentionally * rejects this. */ static int test_DoUserAuthRequestMlDsa_CertPath(const char* keyTypeName) { static const char username[] = "wolfssh"; static const char serviceName[] = "ssh-connection"; static const char authName[] = "publickey"; const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); /* NOTE: pubKeyBlob is an RFC 6187 wire blob, not leaf-cert DER. The real * server path calls ParseLeafCert() first to extract DER. This test * exercises ASN.1-invalid rejection rather than cryptographic rejection — * valid for a negative path test, but does not cover the DER-valid case. */ static const byte junkCert[] = { 0x30, 0x05, 0x00, 0x00, 0x00, 0x00 }; const word32 junkCertSz = (word32)sizeof(junkCert); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WS_UserAuthData authData; byte* pubKeyBlob = NULL; byte* sigBlob = NULL; word32 pubKeyBlobSz, off; int result = 0; int ret; /* RFC 6187 cert chain blob: * string keyTypeName * uint32 certCount=1 * string junkCert * uint32 ocspCount=0 */ pubKeyBlobSz = UINT32_SZ + keyTypeNameSz + UINT32_SZ + UINT32_SZ + junkCertSz + UINT32_SZ; pubKeyBlob = (byte*)WMALLOC(pubKeyBlobSz, NULL, 0); if (pubKeyBlob == NULL) { result = -820; goto done; } off = 0; MlDsaTest_PutLen(pubKeyBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(pubKeyBlob + off, 1); off += UINT32_SZ; MlDsaTest_PutLen(pubKeyBlob + off, junkCertSz); off += UINT32_SZ; WMEMCPY(pubKeyBlob + off, junkCert, junkCertSz); off += junkCertSz; MlDsaTest_PutLen(pubKeyBlob + off, 0); off += UINT32_SZ; /* Minimal sig blob: string keyTypeName || string(1 zero byte) */ sigBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + 1, NULL, 0); if (sigBlob == NULL) { result = -821; goto done; } off = 0; MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(sigBlob + off, 1); off += UINT32_SZ; sigBlob[off] = 0x00; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -822; goto done; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -823; goto done; } ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.username = (const byte*)username; authData.usernameSz = (word32)(sizeof(username) - 1); authData.serviceName = (const byte*)serviceName; authData.serviceNameSz = (word32)(sizeof(serviceName) - 1); authData.authName = (const byte*)authName; authData.authNameSz = (word32)(sizeof(authName) - 1); authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; authData.sf.publicKey.publicKey = pubKeyBlob; authData.sf.publicKey.publicKeySz = pubKeyBlobSz; authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.signature = sigBlob; authData.sf.publicKey.signatureSz = UINT32_SZ * 2 + keyTypeNameSz + 1; ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); if (ret == WS_INVALID_ALGO_ID) { /* Routing failed: x509v3-mldsa-* was not recognised and hit the * unknown-algo guard instead of the cert parse path. */ printf("DoUserAuthRequestMlDsa cert-path (%s): routing failed\n", keyTypeName); result = -824; } else if (ret == WS_SUCCESS) { /* Wrongful acceptance: junk cert should have been rejected */ printf("DoUserAuthRequestMlDsa cert-path (%s): " "wrongfully accepted junk cert\n", keyTypeName); result = -825; } /* Any other error is expected: junk cert correctly rejected. */ done: if (pubKeyBlob != NULL) WFREE(pubKeyBlob, NULL, 0); if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #if defined(WOLFSSH_CERTS) && defined(WOLFSSL_CERT_GEN) /* Positive cert-path test: generate a real ML-DSA self-signed cert, build a * valid auth request, and verify that DoUserAuthRequestMlDsa accepts it. */ static int test_DoUserAuthRequestMlDsa_CertPath_Valid( const char* keyTypeName, byte level, int certSigType) { static const char username[] = "wolfssh"; static const char serviceName[] = "ssh-connection"; static const char authName[] = "publickey"; const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); const word32 usernameSz = (word32)(sizeof(username) - 1); const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); const word32 authNameSz = (word32)(sizeof(authName) - 1); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; MlDsaKey signingKey; int signingKeyInit = 0; WC_RNG rng; int rngInit = 0; Cert myCert; WS_UserAuthData authData; byte* certDER = NULL; byte* rfcBlob = NULL; /* RFC6187 cert chain blob */ byte* sigBlob = NULL; byte* dataToSign = NULL; byte* checkData = NULL; byte* sig = NULL; word32 certDERSz = 0; word32 rfcBlobSz = 0; word32 sigBlobSz = 0; word32 dataToSignSz = 0; word32 checkDataSz = 0; int sigSzInt; word32 sigSz = 0; int mldsaKeyType; word32 off; int result = 0; int ret; mldsaKeyType = (level == WC_ML_DSA_44) ? ML_DSA_44_TYPE : (level == WC_ML_DSA_65) ? ML_DSA_65_TYPE : ML_DSA_87_TYPE; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -850; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -851; goto done; } ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); if (wc_InitRng(&rng) != 0) { result = -852; goto done; } rngInit = 1; if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { result = -853; goto done; } signingKeyInit = 1; if (wc_MlDsaKey_SetParams(&signingKey, level) != 0) { result = -854; goto done; } if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { result = -855; goto done; } /* Generate a self-signed X.509 cert containing the ML-DSA public key. * 16384 bytes is sufficient for the largest ML-DSA variant (ML-DSA-87). */ certDER = (byte*)WMALLOC(16384, NULL, 0); if (certDER == NULL) { result = -856; goto done; } wc_InitCert(&myCert); WSTRNCPY(myCert.subject.commonName, "wolfSSH-mldsa-test", CTC_NAME_SIZE - 1); myCert.subject.commonNameEnc = CTC_UTF8; WSTRNCPY(myCert.subject.country, "US", CTC_NAME_SIZE - 1); myCert.daysValid = 365; myCert.selfSigned = 1; myCert.sigType = certSigType; ret = wc_MakeCert_ex(&myCert, certDER, 16384, mldsaKeyType, &signingKey, &rng); if (ret <= 0) { result = -857; goto done; } ret = wc_SignCert_ex(ret, certSigType, certDER, 16384, mldsaKeyType, &signingKey, &rng); if (ret <= 0) { result = -858; goto done; } certDERSz = (word32)ret; /* Build RFC6187 cert chain blob: * string keyTypeName | uint32 certCount=1 | string certDER | * uint32 ocspCount=0 */ rfcBlobSz = UINT32_SZ + keyTypeNameSz + UINT32_SZ + UINT32_SZ + certDERSz + UINT32_SZ; rfcBlob = (byte*)WMALLOC(rfcBlobSz, NULL, 0); if (rfcBlob == NULL) { result = -859; goto done; } off = 0; MlDsaTest_PutLen(rfcBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(rfcBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(rfcBlob + off, 1); off += UINT32_SZ; MlDsaTest_PutLen(rfcBlob + off, certDERSz); off += UINT32_SZ; WMEMCPY(rfcBlob + off, certDER, certDERSz); off += certDERSz; MlDsaTest_PutLen(rfcBlob + off, 0); off += UINT32_SZ; /* Build dataToSign; pubkey blob length uses RFC6187 blob size. */ dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + 1 + keyTypeNameSz + rfcBlobSz; dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); if (dataToSign == NULL) { result = -860; goto done; } off = 0; MlDsaTest_PutLen(dataToSign + off, usernameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, username, usernameSz); off += usernameSz; MlDsaTest_PutLen(dataToSign + off, serviceNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, serviceName, serviceNameSz); off += serviceNameSz; MlDsaTest_PutLen(dataToSign + off, authNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, authName, authNameSz); off += authNameSz; dataToSign[off++] = 1; /* hasSig */ MlDsaTest_PutLen(dataToSign + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(dataToSign + off, rfcBlobSz); off += UINT32_SZ; WMEMCPY(dataToSign + off, rfcBlob, rfcBlobSz); off += rfcBlobSz; /* Build checkData and sign it. */ sigSzInt = wc_MlDsaKey_SigSize(&signingKey); if (sigSzInt < 0) { result = -861; goto done; } sigSz = (word32)sigSzInt; sig = (byte*)WMALLOC(sigSz, NULL, 0); if (sig == NULL) { result = -871; goto done; } checkDataSz = UINT32_SZ + ssh->sessionIdSz + MSG_ID_SZ + dataToSignSz; checkData = (byte*)WMALLOC(checkDataSz, NULL, 0); if (checkData == NULL) { result = -862; goto done; } off = 0; MlDsaTest_PutLen(checkData + off, ssh->sessionIdSz); off += UINT32_SZ; WMEMCPY(checkData + off, ssh->sessionId, ssh->sessionIdSz); off += ssh->sessionIdSz; checkData[off++] = MSGID_USERAUTH_REQUEST; WMEMCPY(checkData + off, dataToSign, dataToSignSz); if (wc_MlDsaKey_SignCtx(&signingKey, NULL, 0, sig, &sigSz, checkData, checkDataSz, &rng) != 0) { result = -863; goto done; } /* Build signature blob: string keyTypeName || string sig */ sigBlobSz = UINT32_SZ * 2 + keyTypeNameSz + sigSz; sigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); if (sigBlob == NULL) { result = -864; goto done; } off = 0; MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(sigBlob + off, sigSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, sig, sigSz); off += sigSz; sigBlobSz = off; /* DoUserAuthRequestMlDsa with isCert=1 expects pk->publicKey to be the * leaf cert DER. Pass rfcBlobSz explicitly so dataToSign is sized from * the wire blob length, not the cert DER length. */ WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.username = (const byte*)username; authData.usernameSz = usernameSz; authData.serviceName = (const byte*)serviceName; authData.serviceNameSz = serviceNameSz; authData.authName = (const byte*)authName; authData.authNameSz = authNameSz; authData.sf.publicKey.dataToSign = dataToSign; authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; authData.sf.publicKey.publicKey = certDER; authData.sf.publicKey.publicKeySz = certDERSz; authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.isCert = 1; authData.sf.publicKey.signature = sigBlob; authData.sf.publicKey.signatureSz = sigBlobSz; ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, rfcBlobSz); if (ret != WS_SUCCESS) { printf("DoUserAuthRequestMlDsa cert-path valid (%s): " "ret=%d expected %d\n", keyTypeName, ret, WS_SUCCESS); result = -865; } done: if (signingKeyInit) wc_MlDsaKey_Free(&signingKey); if (rngInit) wc_FreeRng(&rng); if (certDER != NULL) WFREE(certDER, NULL, 0); if (rfcBlob != NULL) WFREE(rfcBlob, NULL, 0); if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); if (checkData != NULL) WFREE(checkData, NULL, 0); if (sig != NULL) WFREE(sig, NULL, 0); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } /* Cross-level mismatch: cert has ML-DSA key at a different level than * keyTypeName claims. PublicKeyDecode fails, should return WS_CRYPTO_FAILED. * Requires both MLDSA44 (for the claimed type) and MLDSA65 (for the actual * key embedded in the cert). */ #if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_MLDSA65) static int test_DoUserAuthRequestMlDsa_CertPath_WrongLevel(void) { /* Claim ML-DSA-44 type but embed an ML-DSA-65 key in the cert. */ static const char keyTypeName[] = "x509v3-ssh-mldsa-44"; static const char username[] = "wolfssh"; static const char serviceName[] = "ssh-connection"; static const char authName[] = "publickey"; const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); const word32 usernameSz = (word32)(sizeof(username) - 1); const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); const word32 authNameSz = (word32)(sizeof(authName) - 1); WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; MlDsaKey signingKey; int signingKeyInit = 0; WC_RNG rng; int rngInit = 0; Cert myCert; WS_UserAuthData authData; byte* certDER = NULL; byte* sigBlob = NULL; byte* dataToSign = NULL; word32 certDERSz = 0; word32 dataToSignSz = 0; word32 off; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -880; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -881; goto done; } ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); if (wc_InitRng(&rng) != 0) { result = -882; goto done; } rngInit = 1; if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { result = -883; goto done; } signingKeyInit = 1; if (wc_MlDsaKey_SetParams(&signingKey, WC_ML_DSA_65) != 0) { result = -884; goto done; } if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { result = -885; goto done; } certDER = (byte*)WMALLOC(16384, NULL, 0); if (certDER == NULL) { result = -886; goto done; } wc_InitCert(&myCert); WSTRNCPY(myCert.subject.commonName, "wolfSSH-mldsa-test", CTC_NAME_SIZE - 1); myCert.subject.commonNameEnc = CTC_UTF8; WSTRNCPY(myCert.subject.country, "US", CTC_NAME_SIZE - 1); myCert.daysValid = 365; myCert.selfSigned = 1; myCert.sigType = CTC_ML_DSA_65; ret = wc_MakeCert_ex(&myCert, certDER, 16384, ML_DSA_65_TYPE, &signingKey, &rng); if (ret <= 0) { result = -887; goto done; } ret = wc_SignCert_ex(ret, CTC_ML_DSA_65, certDER, 16384, ML_DSA_65_TYPE, &signingKey, &rng); if (ret <= 0) { result = -888; goto done; } certDERSz = (word32)ret; /* Dummy sig blob: keyTypeName || one zero byte. */ sigBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + 1, NULL, 0); if (sigBlob == NULL) { result = -889; goto done; } off = 0; MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; MlDsaTest_PutLen(sigBlob + off, 1); off += UINT32_SZ; sigBlob[off] = 0x00; /* Sized to match the checkData formula in DoUserAuthRequestMlDsa with * pubKeyBlobSz=0. Populated with zeros - not a valid payload, but * non-NULL so that if key-level enforcement ever relaxes, execution fails * at sig verify rather than crashing on a NULL deref. */ dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + BOOLEAN_SZ + keyTypeNameSz; dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); if (dataToSign == NULL) { result = -890; goto done; } WMEMSET(dataToSign, 0, dataToSignSz); WMEMSET(&authData, 0, sizeof(authData)); authData.type = WOLFSSH_USERAUTH_PUBLICKEY; authData.username = (const byte*)username; authData.usernameSz = usernameSz; authData.serviceName = (const byte*)serviceName; authData.serviceNameSz = serviceNameSz; authData.authName = (const byte*)authName; authData.authNameSz = authNameSz; authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; authData.sf.publicKey.publicKey = certDER; authData.sf.publicKey.publicKeySz = certDERSz; authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.isCert = 1; authData.sf.publicKey.dataToSign = dataToSign; authData.sf.publicKey.signature = sigBlob; authData.sf.publicKey.signatureSz = UINT32_SZ * 2 + keyTypeNameSz + 1; ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, 0); if (ret == WS_SUCCESS) { printf("DoUserAuthRequestMlDsa cert-path wrong-level: " "wrongfully accepted\n"); result = -891; } else if (ret != WS_CRYPTO_FAILED) { printf("DoUserAuthRequestMlDsa cert-path wrong-level: " "ret=%d expected %d\n", ret, WS_CRYPTO_FAILED); result = -892; } done: if (signingKeyInit) wc_MlDsaKey_Free(&signingKey); if (rngInit) wc_FreeRng(&rng); if (certDER != NULL) WFREE(certDER, NULL, 0); if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_NO_MLDSA44 && WOLFSSH_NO_MLDSA65 */ #endif /* WOLFSSH_CERTS && WOLFSSL_CERT_GEN */ #endif /* WOLFSSH_CERTS */ #ifdef WOLFSSH_KEYGEN static int test_PrepareUserAuthRequestMlDsa_Params(word32 keygenLevel, byte keyId, int derBufSz) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WS_UserAuthData authData; WS_KeySignature keySig; byte* derKey = NULL; word32 payloadSz; int derKeySz; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -900; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -901; goto done; } derKey = (byte*)WMALLOC(derBufSz, NULL, 0); if (derKey == NULL) { result = -902; goto done; } derKeySz = wolfSSH_MakeMlDsaKey(derKey, (word32)derBufSz, keygenLevel); if (derKeySz < 0) { result = -903; goto done; } /* DER success path */ WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = derKey; authData.sf.publicKey.privateKeySz = (word32)derKeySz; authData.sf.publicKey.hasSignature = 0; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, &authData, &keySig); if (ret != WS_SUCCESS) { result = -904; goto done; } wc_MlDsaKey_Free(&keySig.ks.mldsa.key); /* Fallback path: garbage fails PrivateKeyDecode, then fails OpenSSH * magic check; function must return an error (not leak the Init'd key). */ { static const byte badKey[] = { 0xFF, 0xFE, 0x00, 0x01 }; WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = badKey; authData.sf.publicKey.privateKeySz = sizeof(badKey); authData.sf.publicKey.hasSignature = 0; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, &authData, &keySig); if (ret == WS_SUCCESS) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -905; goto done; } } /* hasSignature=1 path: exercises payload-size accumulation and sigSz. */ { WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = derKey; authData.sf.publicKey.privateKeySz = (word32)derKeySz; authData.sf.publicKey.hasSignature = 1; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, &authData, &keySig); /* On failure the function frees the key internally; only free on * success paths where the key was left initialized for the caller. */ if (ret != WS_SUCCESS) { result = -906; goto done; } if (keySig.sigSz == 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -907; goto done; } if (payloadSz == 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -908; goto done; } wc_MlDsaKey_Free(&keySig.ks.mldsa.key); } done: WFREE(derKey, NULL, 0); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_KEYGEN */ static int test_DoUserAuthRequestMlDsa(void) { int ret; #ifndef WOLFSSH_NO_MLDSA44 ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-44", WC_ML_DSA_44); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA65 ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-65", WC_ML_DSA_65); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA87 ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-87", WC_ML_DSA_87); if (ret != 0) return ret; #endif ret = test_DoUserAuthRequestMlDsa_BadAlgo(); if (ret != 0) return ret; #ifdef WOLFSSH_CERTS #ifndef WOLFSSH_NO_MLDSA44 ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-44"); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA65 ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-65"); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA87 ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-87"); if (ret != 0) return ret; #endif #ifdef WOLFSSL_CERT_GEN #ifndef WOLFSSH_NO_MLDSA44 ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( "x509v3-ssh-mldsa-44", WC_ML_DSA_44, CTC_ML_DSA_44); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA65 ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( "x509v3-ssh-mldsa-65", WC_ML_DSA_65, CTC_ML_DSA_65); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA87 ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( "x509v3-ssh-mldsa-87", WC_ML_DSA_87, CTC_ML_DSA_87); if (ret != 0) return ret; #endif #if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_MLDSA65) ret = test_DoUserAuthRequestMlDsa_CertPath_WrongLevel(); if (ret != 0) return ret; #endif #endif /* WOLFSSL_CERT_GEN */ #endif /* WOLFSSH_CERTS */ return 0; } #ifdef WOLFSSH_KEYGEN static int test_PrepareUserAuthRequestMlDsa(void) { int ret = 0; #ifndef WOLFSSH_NO_MLDSA44 ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_44, ID_MLDSA44, WC_MLDSA_44_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA65 ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_65, ID_MLDSA65, WC_MLDSA_65_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA87 ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_87, ID_MLDSA87, WC_MLDSA_87_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif (void)ret; return 0; } #ifdef WOLFSSH_CERTS static int test_PrepareUserAuthRequestMlDsaCert_Params(word32 keygenLevel, byte keyId, int derBufSz) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WS_UserAuthData authData; WS_KeySignature keySig; byte* derKey = NULL; word32 payloadSz; int derKeySz; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -920; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -921; goto done; } derKey = (byte*)WMALLOC(derBufSz, NULL, 0); if (derKey == NULL) { result = -922; goto done; } derKeySz = wolfSSH_MakeMlDsaKey(derKey, (word32)derBufSz, keygenLevel); if (derKeySz < 0) { result = -923; goto done; } /* Success path: good key, hasSignature=0 */ WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = derKey; authData.sf.publicKey.privateKeySz = (word32)derKeySz; authData.sf.publicKey.hasSignature = 0; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, &authData, &keySig); if (ret != WS_SUCCESS) { result = -924; goto done; } wc_MlDsaKey_Free(&keySig.ks.mldsa.key); /* Bad key: exercises the PrivateKeyDecode failure free path */ { static const byte badKey[] = { 0xFF, 0xFE, 0x00, 0x01 }; WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = badKey; authData.sf.publicKey.privateKeySz = sizeof(badKey); authData.sf.publicKey.hasSignature = 0; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, &authData, &keySig); if (ret == WS_SUCCESS) { result = -925; goto done; } } /* hasSignature=1 path: exercises sigSz accumulation */ { WMEMSET(&authData, 0, sizeof(authData)); WMEMSET(&keySig, 0, sizeof(keySig)); payloadSz = 0; authData.sf.publicKey.privateKey = derKey; authData.sf.publicKey.privateKeySz = (word32)derKeySz; authData.sf.publicKey.hasSignature = 1; keySig.keyId = keyId; keySig.heap = NULL; ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, &authData, &keySig); if (ret != WS_SUCCESS) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -926; goto done; } if (keySig.sigSz == 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -927; goto done; } if (payloadSz == 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -928; goto done; } wc_MlDsaKey_Free(&keySig.ks.mldsa.key); } done: WFREE(derKey, NULL, 0); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } static int test_PrepareUserAuthRequestMlDsaCert(void) { int ret = 0; #ifndef WOLFSSH_NO_MLDSA44 ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_44, ID_X509V3_MLDSA44, WC_MLDSA_44_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA65 ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_65, ID_X509V3_MLDSA65, WC_MLDSA_65_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif #ifndef WOLFSSH_NO_MLDSA87 ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_87, ID_X509V3_MLDSA87, WC_MLDSA_87_BOTH_KEY_DER_SIZE); if (ret != 0) return ret; #endif (void)ret; return 0; } #endif /* WOLFSSH_CERTS */ #endif /* WOLFSSH_KEYGEN */ static int test_BuildUserAuthRequestMlDsa(void) { #ifndef WOLFSSH_NO_MLDSA44 WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WS_KeySignature keySig; WS_UserAuthData authData; byte output[MLDSA_MAX_SIG_SIZE + 256]; word32 idx = 0; word32 idx0 = 0; word32 idxBefore = 0; int sigSz; int result = 0; int ret; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) { result = -700; goto done; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -701; goto done; } ssh->sessionIdSz = 16; WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); WMEMSET(&keySig, 0, sizeof(keySig)); keySig.keyId = ID_MLDSA44; keySig.heap = NULL; ret = wc_MlDsaKey_Init(&keySig.ks.mldsa.key, NULL, INVALID_DEVID); if (ret != 0) { result = -702; goto done; } { word32 kidx = 0; ret = wc_MlDsaKey_PrivateKeyDecode(&keySig.ks.mldsa.key, unitTestMlDsaPrivKey, (word32)sizeof(unitTestMlDsaPrivKey), &kidx); } if (ret != 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -703; goto done; } sigSz = wc_MlDsaKey_SigSize(&keySig.ks.mldsa.key); if (sigSz < 0) { wc_MlDsaKey_Free(&keySig.ks.mldsa.key); result = -704; goto done; } keySig.sigSz = (word32)sigSz; WMEMSET(&authData, 0, sizeof(authData)); authData.sf.publicKey.hasSignature = 1; authData.sf.publicKey.publicKeyType = (const byte*)"ssh-mldsa-44"; authData.sf.publicKey.publicKeyTypeSz = 12; /* Write minimal auth-request header so (begin - sigStartIdx) > 0. */ { static const char testUser[] = "user"; static const char testSvc[] = "ssh-connection"; static const char testMethod[] = "publickey"; output[idx++] = MSGID_USERAUTH_REQUEST; MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testUser)); idx += UINT32_SZ; WMEMCPY(output + idx, testUser, WSTRLEN(testUser)); idx += (word32)WSTRLEN(testUser); MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testSvc)); idx += UINT32_SZ; WMEMCPY(output + idx, testSvc, WSTRLEN(testSvc)); idx += (word32)WSTRLEN(testSvc); MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testMethod)); idx += UINT32_SZ; WMEMCPY(output + idx, testMethod, WSTRLEN(testMethod)); idx += (word32)WSTRLEN(testMethod); } idxBefore = idx; ret = wolfSSH_TestBuildUserAuthRequestMlDsa(ssh, output, &idx, &authData, output, idx0, &keySig); wc_MlDsaKey_Free(&keySig.ks.mldsa.key); if (ret != WS_SUCCESS) { printf("BuildUserAuthRequestMlDsa failed: %d\n", ret); result = -705; } else { /* 3 length fields + algo name + sig bytes */ word32 nameSz = (word32)WSTRLEN("ssh-mldsa-44"); word32 expAdv = 3 * UINT32_SZ + nameSz + (word32)sigSz; if (idx - idxBefore != expAdv) { printf("BuildUserAuthRequestMlDsa idx advance wrong:" " got %d expected %d\n", (int)(idx - idxBefore), (int)expAdv); result = -706; } } done: if (ssh) wolfSSH_free(ssh); if (ctx) wolfSSH_CTX_free(ctx); return result; #else return 0; #endif } #endif /* IdentifyAsn1Key unit test * * Exercises every new wc_Free* error-path added in IdentifyAsn1Key: * - wc_FreeRsaKey on RSA decode failure * - wc_ecc_free on ECC decode failure * - wc_ecc_free in the default: branch (unsupported curve) * - wc_ed25519_free on Ed25519 decode failure * Each happy-path call implicitly exercises the failure-path frees for the * other key types. */ static int test_IdentifyAsn1Key(void) { int result = 0; int ret; #ifndef WOLFSSH_NO_RSA ret = IdentifyAsn1Key(unitTestRsaPrivKey, unitTestRsaPrivKeySz, 1, NULL, NULL); if (ret != ID_SSH_RSA) { printf("IdentifyAsn1Key: RSA priv failed, ret=%d\n", ret); result = -600; goto done; } #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 ret = IdentifyAsn1Key(unitTestEcc256PrivKey, (word32)sizeof(unitTestEcc256PrivKey), 1, NULL, NULL); if (ret != ID_ECDSA_SHA2_NISTP256) { printf("IdentifyAsn1Key: ECC P-256 priv failed, ret=%d\n", ret); result = -601; goto done; } #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384 ret = IdentifyAsn1Key(unitTestEcc384PrivKey, (word32)sizeof(unitTestEcc384PrivKey), 1, NULL, NULL); if (ret != ID_ECDSA_SHA2_NISTP384) { printf("IdentifyAsn1Key: ECC P-384 priv failed, ret=%d\n", ret); result = -602; goto done; } #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 ret = IdentifyAsn1Key(unitTestEcc521PrivKey, (word32)sizeof(unitTestEcc521PrivKey), 1, NULL, NULL); if (ret != ID_ECDSA_SHA2_NISTP521) { printf("IdentifyAsn1Key: ECC P-521 priv failed, ret=%d\n", ret); result = -603; goto done; } #endif #if !defined(WOLFSSH_NO_ED25519) ret = IdentifyAsn1Key(unitTestEd25519PrivKey, (word32)sizeof(unitTestEd25519PrivKey), 1, NULL, NULL); if (ret != ID_ED25519) { printf("IdentifyAsn1Key: Ed25519 priv failed, ret=%d\n", ret); result = -604; goto done; } #endif #if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) ret = IdentifyAsn1Key(unitTestMlDsaPrivKey, (word32)sizeof(unitTestMlDsaPrivKey), 1, NULL, NULL); if (ret != ID_MLDSA44) { printf("IdentifyAsn1Key: MlDsa priv failed, ret=%d\n", ret); result = -606; goto done; } /* Raw public key probe path: extract the public key from the private key * test vector, then pass the raw bytes (no SPKI wrapper) to * IdentifyAsn1Key. This exercises the level-probing fallback added for * certificate-extracted public keys. */ { MlDsaKey mlKey; byte* mlPub = NULL; word32 mlPubSz = 0; word32 mlIdx = 0; if (wc_MlDsaKey_Init(&mlKey, NULL, INVALID_DEVID) != 0) { result = -607; goto done; } if (wc_MlDsaKey_PrivateKeyDecode(&mlKey, unitTestMlDsaPrivKey, sizeof(unitTestMlDsaPrivKey), &mlIdx) != 0) { wc_MlDsaKey_Free(&mlKey); result = -608; goto done; } { int mlPubSzInt = wc_MlDsaKey_PubSize(&mlKey); if (mlPubSzInt < 0) { wc_MlDsaKey_Free(&mlKey); result = -609; goto done; } mlPubSz = (word32)mlPubSzInt; } mlPub = (byte*)WMALLOC(mlPubSz, NULL, 0); if (mlPub == NULL) { wc_MlDsaKey_Free(&mlKey); result = -619; goto done; } if (wc_MlDsaKey_ExportPubRaw(&mlKey, mlPub, &mlPubSz) != 0) { WFREE(mlPub, NULL, 0); wc_MlDsaKey_Free(&mlKey); result = -610; goto done; } wc_MlDsaKey_Free(&mlKey); ret = IdentifyAsn1Key(mlPub, mlPubSz, 0, NULL, NULL); WFREE(mlPub, NULL, 0); if (ret != ID_MLDSA44) { printf("IdentifyAsn1Key: MlDsa raw pub probe failed, ret=%d\n", ret); result = -611; goto done; } } #endif /* Unsupported ECC curve: triggers wc_ecc_free in the default: branch * (wolfSSL has P-192) or the else branch (wolfSSL lacks P-192). * Either way the key must be freed and WS_UNIMPLEMENTED_E returned. */ #ifndef WOLFSSH_NO_ECDSA ret = IdentifyAsn1Key(unitTestEccUnsupportedCurveKey, (word32)sizeof(unitTestEccUnsupportedCurveKey), 1, NULL, NULL); if (ret != WS_UNIMPLEMENTED_E) { printf("IdentifyAsn1Key: unsupported ECC curve expected " "WS_UNIMPLEMENTED_E, got %d\n", ret); result = -605; goto done; } #endif /* Garbage: all decode attempts fail, all wc_Free* cleanup paths hit */ { static const byte garbage[] = {0x00, 0x01, 0x02, 0x03}; ret = IdentifyAsn1Key(garbage, (word32)sizeof(garbage), 1, NULL, NULL); if (ret != WS_UNIMPLEMENTED_E) { printf("IdentifyAsn1Key: garbage expected WS_UNIMPLEMENTED_E, " "got %d\n", ret); result = -606; goto done; } } done: return result; } #ifdef WOLFSSH_TEST_CERTMAN_PROMOTE /* Read a whole file into a freshly malloc'd buffer. Caller frees *buf. */ static int certmanLoadFile(const char* fn, byte** buf, word32* bufSz) { FILE* f; long sz; size_t rd; *buf = NULL; *bufSz = 0; f = fopen(fn, "rb"); if (f == NULL) return -1; if (fseek(f, 0, SEEK_END) != 0 || (sz = ftell(f)) < 0) { fclose(f); return -1; } #if LONG_MAX > 0xFFFFFFFFL /* The size is later stored in a word32; reject anything that would not * round-trip so *bufSz stays consistent with the allocated/read size. */ if (sz > 0xFFFFFFFFL) { fclose(f); return -1; } #endif rewind(f); *buf = (byte*)malloc((size_t)sz); if (*buf == NULL) { fclose(f); return -1; } rd = fread(*buf, 1, (size_t)sz, f); fclose(f); if (rd != (size_t)sz) { free(*buf); *buf = NULL; return -1; } *bufSz = (word32)sz; return 0; } /* Forge an end-entity cert whose issuer is the supplied cert and which is * signed with the supplied (non-CA) key. Fills der/derSz on success. */ static int certmanForgeChild(const byte* issuerCert, word32 issuerCertSz, const byte* issuerKey, word32 issuerKeySz, byte* der, word32* derSz) { ecc_key key; WC_RNG rng; Cert cert; word32 idx = 0; int ret; int sz; int haveKey = 0, haveRng = 0; ret = wc_ecc_init(&key); if (ret == 0) { haveKey = 1; ret = wc_InitRng(&rng); } if (ret == 0) { haveRng = 1; ret = wc_EccPrivateKeyDecode(issuerKey, &idx, &key, issuerKeySz); } if (ret == 0) ret = wc_InitCert(&cert); if (ret == 0) { WSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); WSTRNCPY(cert.subject.commonName, "Mallory", CTC_NAME_SIZE); cert.sigType = CTC_SHA256wECDSA; cert.daysValid = 365; cert.isCA = 0; ret = wc_SetIssuerBuffer(&cert, issuerCert, (int)issuerCertSz); } if (ret == 0) { /* Reuse the issuer key as the subject key; only the issuer and the * signature matter for the signer lookup under test. */ sz = wc_MakeCert(&cert, der, *derSz, NULL, &key, &rng); if (sz < 0) ret = sz; } if (ret == 0) { sz = wc_SignCert(cert.bodySz, cert.sigType, der, *derSz, NULL, &key, &rng); if (sz < 0) ret = sz; else *derSz = (word32)sz; } if (haveRng) wc_FreeRng(&rng); if (haveKey) wc_ecc_free(&key); return ret; } /* Forge a cert with the given subject CN. When isCA is set the cert asserts * basicConstraints CA=TRUE. When keyUsage is non-NULL the cert carries a * KeyUsage extension with the named usage(s) (e.g. "keyCertSign" or * "digitalSignature"); NULL omits the extension entirely. The issuer name is * taken from issuerCert, the subject public key from subjectKey, and the cert * is signed with issuerKey. Fills der/derSz on success. */ static int certmanForgeCert(const char* cn, int isCA, const char* keyUsage, const byte* issuerCert, word32 issuerCertSz, ecc_key* issuerKey, ecc_key* subjectKey, byte* der, word32* derSz) { WC_RNG rng; Cert cert; int ret; int sz; int haveRng = 0; ret = wc_InitRng(&rng); if (ret == 0) { haveRng = 1; ret = wc_InitCert(&cert); } if (ret == 0) { /* wc_InitCert zeroed the struct, so leaving the final byte untouched * keeps the name NUL-terminated and avoids a strncpy truncation * warning on the runtime cn pointer. */ WSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); WSTRNCPY(cert.subject.commonName, cn, CTC_NAME_SIZE - 1); cert.sigType = CTC_SHA256wECDSA; cert.daysValid = 365; cert.isCA = isCA; if (keyUsage != NULL) { #ifdef WOLFSSL_CERT_EXT ret = wc_SetKeyUsage(&cert, keyUsage); #else ret = BAD_FUNC_ARG; #endif } } if (ret == 0) ret = wc_SetIssuerBuffer(&cert, issuerCert, (int)issuerCertSz); if (ret == 0) { sz = wc_MakeCert(&cert, der, *derSz, NULL, subjectKey, &rng); if (sz < 0) ret = sz; } if (ret == 0) { sz = wc_SignCert(cert.bodySz, cert.sigType, der, *derSz, NULL, issuerKey, &rng); if (sz < 0) ret = sz; else *derSz = (word32)sz; } if (haveRng) wc_FreeRng(&rng); return ret; } static void certmanPutU32(byte* p, word32 v) { p[0] = (byte)(v >> 24); p[1] = (byte)(v >> 16); p[2] = (byte)(v >> 8); p[3] = (byte)(v); } /* Append a length-prefixed cert (the framing VerifyCerts_buffer expects). * chainCap is the capacity of chain; on insufficient space the chain is left * unchanged so a cert-size regression surfaces as a failed assertion rather * than memory corruption. The subtractions are ordered to avoid word32 * underflow. */ static word32 certmanAppendCert(byte* chain, word32 chainCap, word32 chainSz, const byte* cert, word32 certSz) { if (chainSz > chainCap || certSz > chainCap - chainSz || UINT32_SZ > chainCap - chainSz - certSz) { return chainSz; } certmanPutU32(chain + chainSz, certSz); chainSz += UINT32_SZ; WMEMCPY(chain + chainSz, cert, certSz); chainSz += certSz; return chainSz; } /* Regression test: a peer-supplied end-entity (non-CA) * certificate presented at an intermediate position in a chain must not be * promoted into the cert manager's trust store. If it were, the holder of any * ordinary leaf cert issued by a trusted root could then forge certs binding * arbitrary SSH principals. * * Fred's cert is a non-CA leaf issued by the test root (ca-cert-ecc). We forge * a child ("Mallory") issued by Fred and signed with Fred's key, then: * 1. Sanity: with Fred explicitly trusted as a CA, the forged child gets * past the no-signer stage, proving the child is well-formed and chains * to Fred -- so the regression check below cannot pass vacuously. * 2. Regression: run the attack chain [child, fred] through verify (a * vulnerable build promotes Fred here), then confirm the store was not * mutated -- verifying the child alone must now fail with * WS_CERT_NO_SIGNER_E. */ static int test_CertMan_NoPromoteNonCaIntermediate(void) { int result = 0; int ret; byte* root = NULL; byte* fred = NULL; byte* fredKey = NULL; word32 rootSz = 0, fredSz = 0, fredKeySz = 0; byte child[2048]; word32 childSz = sizeof(child); byte chain[6144]; word32 chainSz; WOLFSSH_CERTMAN* cm = NULL; if (certmanLoadFile("./keys/ca-cert-ecc.der", &root, &rootSz) != 0) { printf("CertMan: can't load root cert\n"); result = -900; goto done; } if (certmanLoadFile("./keys/fred-cert.der", &fred, &fredSz) != 0) { printf("CertMan: can't load fred cert\n"); result = -901; goto done; } if (certmanLoadFile("./keys/fred-key.der", &fredKey, &fredKeySz) != 0) { printf("CertMan: can't load fred key\n"); result = -902; goto done; } ret = certmanForgeChild(fred, fredSz, fredKey, fredKeySz, child, &childSz); if (ret != 0) { printf("CertMan: forge child failed, ret=%d\n", ret); result = -903; goto done; } /* 1. Sanity: with Fred explicitly trusted, the child chains to Fred. */ cm = wolfSSH_CERTMAN_new(NULL); if (cm == NULL) { result = -904; goto done; } if (wolfSSH_CERTMAN_LoadRootCA_buffer(cm, root, rootSz) != WS_SUCCESS) { result = -905; goto done; } if (wolfSSH_CERTMAN_LoadRootCA_buffer(cm, fred, fredSz) != WS_SUCCESS) { result = -906; goto done; } chainSz = certmanAppendCert(chain, (word32)sizeof(chain), 0, child, childSz); ret = wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 1); if (ret == WS_CERT_NO_SIGNER_E) { printf("CertMan: sanity check failed, child didn't chain to Fred\n"); result = -907; goto done; } wolfSSH_CERTMAN_free(cm); cm = NULL; /* 2. Regression: Fred NOT explicitly trusted. Run the attack chain so a * vulnerable build would promote Fred, then confirm the store was not * mutated. */ cm = wolfSSH_CERTMAN_new(NULL); if (cm == NULL) { result = -908; goto done; } if (wolfSSH_CERTMAN_LoadRootCA_buffer(cm, root, rootSz) != WS_SUCCESS) { result = -909; goto done; } chainSz = certmanAppendCert(chain, (word32)sizeof(chain), 0, child, childSz); chainSz = certmanAppendCert(chain, (word32)sizeof(chain), chainSz, fred, fredSz); /* The attack chain itself must be rejected. */ ret = wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 2); if (ret == WS_SUCCESS) { printf("CertMan: attack chain unexpectedly verified\n"); result = -910; goto done; } /* The trust store must be unchanged: verifying the forged child alone must * now fail with no available signer, proving Fred was not promoted. */ chainSz = certmanAppendCert(chain, (word32)sizeof(chain), 0, child, childSz); ret = wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 1); if (ret != WS_CERT_NO_SIGNER_E) { printf("CertMan: non-CA intermediate was promoted! ret=%d\n", ret); result = -911; goto done; } done: if (cm != NULL) wolfSSH_CERTMAN_free(cm); free(root); free(fred); free(fredKey); return result; } /* Drives CertManIntermediateIsCA through a forged [leaf <- intermediate] chain * with only the root trusted, so the intermediate CA must be promoted for the * leaf to find a signer. * * interKeyUsage selects the intermediate's KeyUsage extension: * NULL omits KeyUsage entirely. Pins the extKeyUsageSet guard: * without it the intermediate (extKeyUsage==0) would be * wrongly demoted to non-CA. * "keyCertSign" the RFC 5280 conforming CA case (needs cert-ext support). * "digitalSignature" has KeyUsage but lacks keyCertSign: intermediate * CA must be demoted (keyCertSign-rejection branch). * * expectPromote==1 asserts the intermediate was promoted (verify returns * anything but WS_CERT_NO_SIGNER_E); ==0 asserts it was not (verify returns * WS_CERT_NO_SIGNER_E). Promotion is the unit under test, not full chain * success: with FPKI profile enforcement (--enable-all) a promoted chain's * synthetic leaf is rejected later with WS_CERT_PROFILE_E, which is * orthogonal -- hence the "anything but WS_CERT_NO_SIGNER_E" success criterion * mirroring the negative test's sanity check. */ static int certmanCheckIntermediate(const char* interKeyUsage, int expectPromote) { int result = 0; int ret; byte* root = NULL; byte* rootKeyBuf = NULL; word32 rootSz = 0, rootKeySz = 0; byte inter[2048]; word32 interSz = sizeof(inter); byte leaf[2048]; word32 leafSz = sizeof(leaf); byte chain[6144]; word32 chainSz; word32 idx; ecc_key rootKey, interKey, leafKey; int haveRootKey = 0, haveInterKey = 0, haveLeafKey = 0; WC_RNG rng; int haveRng = 0; WOLFSSH_CERTMAN* cm = NULL; if (certmanLoadFile("./keys/ca-cert-ecc.der", &root, &rootSz) != 0) { printf("CertMan: can't load root cert\n"); result = -920; goto done; } if (certmanLoadFile("./keys/ca-key-ecc.der", &rootKeyBuf, &rootKeySz) != 0) { printf("CertMan: can't load root key\n"); result = -921; goto done; } if (wc_InitRng(&rng) != 0) { result = -922; goto done; } haveRng = 1; /* Only the root key must match the trusted root cert; the intermediate and * leaf use freshly generated keys. */ if (wc_ecc_init(&rootKey) != 0) { result = -923; goto done; } haveRootKey = 1; idx = 0; if (wc_EccPrivateKeyDecode(rootKeyBuf, &idx, &rootKey, rootKeySz) != 0) { result = -924; goto done; } if (wc_ecc_init(&interKey) != 0) { result = -925; goto done; } haveInterKey = 1; if (wc_ecc_make_key(&rng, 32, &interKey) != 0) { result = -926; goto done; } if (wc_ecc_init(&leafKey) != 0) { result = -927; goto done; } haveLeafKey = 1; if (wc_ecc_make_key(&rng, 32, &leafKey) != 0) { result = -928; goto done; } /* Intermediate CA signed by the root. */ ret = certmanForgeCert("IntermediateCA", 1, interKeyUsage, root, rootSz, &rootKey, &interKey, inter, &interSz); if (ret != 0) { printf("CertMan: forge intermediate failed, ret=%d\n", ret); result = -929; goto done; } /* Leaf signed by the intermediate. */ ret = certmanForgeCert("ValidLeaf", 0, NULL, inter, interSz, &interKey, &leafKey, leaf, &leafSz); if (ret != 0) { printf("CertMan: forge leaf failed, ret=%d\n", ret); result = -930; goto done; } /* Trust only the root; the valid intermediate CA must be promoted so the * leaf chains all the way up. */ cm = wolfSSH_CERTMAN_new(NULL); if (cm == NULL) { result = -931; goto done; } if (wolfSSH_CERTMAN_LoadRootCA_buffer(cm, root, rootSz) != WS_SUCCESS) { result = -932; goto done; } chainSz = certmanAppendCert(chain, (word32)sizeof(chain), 0, leaf, leafSz); chainSz = certmanAppendCert(chain, (word32)sizeof(chain), chainSz, inter, interSz); ret = wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 2); if (expectPromote && ret == WS_CERT_NO_SIGNER_E) { printf("CertMan: valid intermediate CA not promoted, ret=%d\n", ret); result = -933; goto done; } if (!expectPromote && ret != WS_CERT_NO_SIGNER_E) { printf("CertMan: intermediate CA without keyCertSign was promoted, " "ret=%d\n", ret); result = -934; goto done; } done: if (cm != NULL) wolfSSH_CERTMAN_free(cm); if (haveRootKey) wc_ecc_free(&rootKey); if (haveInterKey) wc_ecc_free(&interKey); if (haveLeafKey) wc_ecc_free(&leafKey); if (haveRng) wc_FreeRng(&rng); free(root); free(rootKeyBuf); return result; } static int test_CertMan_PromoteValidCaIntermediate(void) { int result; /* A CA intermediate with no KeyUsage extension must still be promoted. */ result = certmanCheckIntermediate(NULL, 1); #ifdef WOLFSSL_CERT_EXT /* As must a CA intermediate that explicitly asserts keyCertSign. */ if (result == 0) result = certmanCheckIntermediate("keyCertSign", 1); #ifndef ALLOW_INVALID_CERTSIGN /* But a CA intermediate carrying a KeyUsage extension that omits * keyCertSign must be rejected (the keyCertSign-rejection branch). */ if (result == 0) result = certmanCheckIntermediate("digitalSignature", 0); #endif #endif return result; } #endif /* WOLFSSH_TEST_CERTMAN_PROMOTE */ /* Tests below install a custom allocator via wolfSSL_SetAllocators. The * wolfSSL_Malloc_cb / wolfSSL_Free_cb / wolfSSL_Realloc_cb typedefs gain * extra parameters when wolfSSL is built with WOLFSSL_STATIC_MEMORY or * WOLFSSL_DEBUG_MEMORY, so the capturing-allocator tests only compile * against the default signature. */ #if !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) #define WOLFSSH_TEST_CAPTURING_ALLOCATOR /* Retain-on-free allocator. Pass-through malloc/realloc; the free * callback unconditionally diverts the pointer onto a retain list so * the buffer's contents can be inspected after the API under test * has called free on it. Install only across the narrow window of * interest so allocations made before/after use the default allocator * and can be paired with default free. */ typedef struct RetainedBuf { void* ptr; struct RetainedBuf* next; } RetainedBuf; static RetainedBuf* retainedFrees = NULL; static void* RetainMalloc(size_t size) { return malloc(size); } static void RetainFree(void* ptr) { RetainedBuf* node; if (ptr == NULL) return; node = (RetainedBuf*)malloc(sizeof(*node)); if (node == NULL) { /* On bookkeeping failure, fall through to a real free; the * test will not be able to inspect this buffer but we do not * leak the underlying allocation. */ free(ptr); return; } node->ptr = ptr; node->next = retainedFrees; retainedFrees = node; } static void* RetainRealloc(void* ptr, size_t size) { return realloc(ptr, size); } static int IsRetained(void* p) { RetainedBuf* r; for (r = retainedFrees; r != NULL; r = r->next) { if (r->ptr == p) return 1; } return 0; } static void DrainRetained(void) { RetainedBuf* r = retainedFrees; while (r != NULL) { RetainedBuf* next = r->next; free(r->ptr); free(r); r = next; } retainedFrees = NULL; } /* Verify SshResourceFree wipes secrets that live inside the WOLFSSH struct * before the struct is released: * - ssh->k: the DH/ECDH shared secret * - ssh->keys: active session encryption + MAC keys (our direction) * - ssh->peerKeys: active session encryption + MAC keys (peer direction) * Mutation testing flagged each ForceZero in SshResourceFree as having no * coverage; removing any of them would leave key material in heap memory * after wolfSSH_free. To inspect the bytes after free without touching * freed memory, the test installs the retain-on-free allocator just * around wolfSSH_free so its frees are diverted onto a retain list. */ static int test_SshResourceFree_zeroesSecrets(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; word32 markedSz; word32 i; const byte* keysBytes; const byte* peerKeysBytes; int result = 0; int retainInstalled = 0; wolfSSL_Malloc_cb prevMf = NULL; wolfSSL_Free_cb prevFf = NULL; wolfSSL_Realloc_cb prevRf = NULL; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -700; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -701; goto out; } markedSz = (word32)sizeof(ssh->k); WMEMSET(ssh->k, 0xA5, markedSz); ssh->kSz = markedSz; WMEMSET(&ssh->keys, 0xA5, sizeof(ssh->keys)); WMEMSET(&ssh->peerKeys, 0xA5, sizeof(ssh->peerKeys)); wolfSSL_GetAllocators(&prevMf, &prevFf, &prevRf); /* Allocators unchanged on failure; nothing to restore. */ if (wolfSSL_SetAllocators(RetainMalloc, RetainFree, RetainRealloc) != 0) { result = -702; goto out; } retainInstalled = 1; wolfSSH_free(ssh); wolfSSL_SetAllocators(prevMf, prevFf, prevRf); retainInstalled = 0; if (!IsRetained(ssh)) { result = -703; goto out; } for (i = 0; i < markedSz; i++) { if (ssh->k[i] != 0) { result = -704; goto out; } } keysBytes = (const byte*)&ssh->keys; for (i = 0; i < (word32)sizeof(ssh->keys); i++) { if (keysBytes[i] != 0) { result = -705; goto out; } } peerKeysBytes = (const byte*)&ssh->peerKeys; for (i = 0; i < (word32)sizeof(ssh->peerKeys); i++) { if (peerKeysBytes[i] != 0) { result = -706; goto out; } } out: if (retainInstalled) wolfSSL_SetAllocators(prevMf, prevFf, prevRf); DrainRetained(); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_TEST_CAPTURING_ALLOCATOR */ #ifndef WOLFSSH_NO_DH /* Verify KeyAgreeDh_client zeroes the ephemeral DH private key * ssh->handshake->x before returning. The ForceZero is unconditional in * the function (runs even if wc_DhAgree fails), so the test does not need * to feed a valid peer public key - it just needs to observe that x is * wiped after the call returns. The test hook wolfSSH_TestKeyAgreeDh_client * exposes the static function. */ static int test_KeyAgreeDh_client_zeroesEphemeralPrivKey(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; HandshakeInfo* hs = NULL; byte bogusF[256]; word32 markedSz; word32 i; int result = 0; int dhInited = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -710; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -711; } /* wolfSSH_new already allocated and zero-initialised ssh->handshake; * use it directly. wolfSSH_free will release it via HandshakeInfoFree. */ hs = ssh->handshake; if (hs == NULL) { result = -712; goto cleanup; } if (wc_InitDhKey(&hs->privKey.dh) != 0) { result = -713; goto cleanup; } dhInited = 1; markedSz = (word32)sizeof(hs->x); WMEMSET(hs->x, 0xA5, markedSz); hs->xSz = markedSz; /* No prime group is set, so wc_DhCheckPubKey fails before wc_DhAgree is * reached. The ForceZero on x is unconditional and runs regardless. */ WMEMSET(bogusF, 0xCC, sizeof(bogusF)); (void)wolfSSH_TestKeyAgreeDh_client(ssh, WC_HASH_TYPE_SHA256, bogusF, (word32)sizeof(bogusF)); /* wc_FreeDhKey was called inside the test hook; do not free again. */ dhInited = 0; for (i = 0; i < markedSz; i++) { if (hs->x[i] != 0) { result = -714; break; } } cleanup: if (dhInited) wc_FreeDhKey(&ssh->handshake->privKey.dh); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* Verify KeyAgreeDh_server rejects a degenerate peer DH public key. * The peer public 'e' is read from ssh->handshake->e; a value of 1 sits at * the low end of RFC 4253 section 8's permitted [1, p-1] range, but it is a * weak/degenerate value that wc_DhCheckPubKey rejects (stricter than the * RFC's bare minimum) before the shared secret is computed. Without that * guard wc_DhAgree would happily derive the degenerate secret 1. */ static int test_KeyAgreeDh_server_rejectsBadPeerPublic(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte f[MAX_KEX_KEY_SZ]; word32 fSz = (word32)sizeof(f); int result = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -730; ssh = wolfSSH_new(ctx); if (ssh == NULL || ssh->handshake == NULL) { result = -731; goto out; } #ifndef WOLFSSH_NO_DH_GROUP14_SHA256 ssh->handshake->kexId = ID_DH_GROUP14_SHA256; #elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512) ssh->handshake->kexId = ID_DH_GROUP16_SHA512; #elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1) ssh->handshake->kexId = ID_DH_GROUP14_SHA1; #elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1) ssh->handshake->kexId = ID_DH_GROUP1_SHA1; #else /* No fixed DH group enabled; fail rather than silently pass without * exercising the peer-key check. */ result = -733; goto out; #endif /* Degenerate peer public key e = 1 (yields the shared secret 1). */ ssh->handshake->e[0] = 0x01; ssh->handshake->eSz = 1; if (wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz) == 0) { /* The degenerate peer public must not be accepted. */ result = -732; } out: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } /* Verify KeyAgreeDh_client rejects a degenerate peer DH public key. The * peer public 'f' is passed straight to the function; a value of 1 sits at the * low end of RFC 4253 section 8's permitted [1, p-1] range, but it is a * weak/degenerate value that the wc_DhCheckPubKey guard rejects (stricter than * the RFC's bare minimum) before the shared secret is used. * wolfSSH_TestSetDhKexKey installs the prime * group and generates a real ephemeral key pair first, mirroring SendKexDhInit, * so the rejection is driven by the bad peer key rather than a missing private * exponent. This guard is defense-in-depth: wc_DhAgree in wolfCrypt also * enforces the same range, so the test confirms the client rejection behavior * but does not isolate the wolfSSH check from the underlying wolfCrypt one. */ static int test_KeyAgreeDh_client_rejectsBadPeerPublic(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte f[1]; int result = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -750; ssh = wolfSSH_new(ctx); if (ssh == NULL || ssh->handshake == NULL) { result = -751; goto out; } #ifndef WOLFSSH_NO_DH_GROUP14_SHA256 ssh->handshake->kexId = ID_DH_GROUP14_SHA256; #elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512) ssh->handshake->kexId = ID_DH_GROUP16_SHA512; #elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1) ssh->handshake->kexId = ID_DH_GROUP14_SHA1; #elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1) ssh->handshake->kexId = ID_DH_GROUP1_SHA1; #else /* No fixed DH group enabled; fail rather than silently pass without * exercising the peer-key check. */ result = -754; goto out; #endif /* Install the prime group on the client's ephemeral DH key, as the client * KEX path does before KeyAgreeDh_client runs. */ if (wolfSSH_TestSetDhKexKey(ssh) != 0) { result = -752; goto out; } /* Degenerate peer public key f = 1 (yields the shared secret 1). */ f[0] = 0x01; if (wolfSSH_TestKeyAgreeDh_client(ssh, WC_HASH_TYPE_SHA256, f, (word32)sizeof(f)) == 0) { /* The degenerate peer public must not be accepted. */ result = -753; } out: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #if defined(WOLFSSH_SMALL_STACK) && defined(WOLFSSH_TEST_CAPTURING_ALLOCATOR) /* Size-tracked, poisoning capture allocator. AllocHeader stores the * user-requested size in front of each allocation so tests can filter * captured buffers by size after the API under test has freed them. * Every fresh allocation is stamped with 0xCC so tests can tell "byte * was untouched" apart from "byte was written and later zeroed". The * free callback unconditionally diverts the pointer (with its size) * onto a capture list. Install only across the narrow window of the * call under test so allocations made outside use the default allocator * and don't need header-aware free on exit. */ typedef struct AllocHeader { size_t size; /* sizeof(AllocHeader) is 2 * sizeof(size_t), which preserves the * alignment that the system malloc returns (16 on 64-bit, 8 on * 32-bit) for the user pointer that follows. */ size_t pad; } AllocHeader; typedef struct CapturedBuf { void* ptr; size_t size; struct CapturedBuf* next; } CapturedBuf; static CapturedBuf* capturedFrees = NULL; static void* CaptureMalloc(size_t size) { AllocHeader* h = (AllocHeader*)malloc(size + sizeof(AllocHeader)); if (h == NULL) return NULL; h->size = size; WMEMSET((void*)(h + 1), 0xCC, size); return (void*)(h + 1); } static void CaptureFree(void* ptr) { AllocHeader* h; CapturedBuf* node; if (ptr == NULL) return; h = (AllocHeader*)ptr - 1; node = (CapturedBuf*)malloc(sizeof(*node)); if (node == NULL) { /* On bookkeeping failure, fall through to a real free; the * test will not be able to inspect this buffer but we do not * leak the underlying allocation. */ free(h); return; } node->ptr = ptr; node->size = h->size; node->next = capturedFrees; capturedFrees = node; } static void* CaptureRealloc(void* ptr, size_t size) { AllocHeader* h; AllocHeader* h2; size_t oldSize; if (ptr == NULL) return CaptureMalloc(size); h = (AllocHeader*)ptr - 1; oldSize = h->size; h2 = (AllocHeader*)realloc(h, size + sizeof(AllocHeader)); if (h2 == NULL) return NULL; h2->size = size; if (size > oldSize) WMEMSET((byte*)(h2 + 1) + oldSize, 0xCC, size - oldSize); return (void*)(h2 + 1); } static void DrainCaptured(void) { CapturedBuf* c = capturedFrees; while (c != NULL) { CapturedBuf* next = c->next; free((AllocHeader*)c->ptr - 1); free(c); c = next; } capturedFrees = NULL; } /* Verify KeyAgreeDh_server zeroes the ephemeral DH private key buffer * y_ptr before WFREE returns it to the heap. y_ptr is a stack array in * the default build but a heap allocation under WOLFSSH_SMALL_STACK, so * the test installs the capturing allocator and inspects the captured * buffer afterwards. * * wc_DhGenerateKeyPair writes only the leading ySz bytes of the * MAX_KEX_KEY_SZ allocation (ySz is typically the prime-group size, well * below MAX_KEX_KEY_SZ), and ForceZero only wipes those same ySz bytes - * so the tail of the buffer is never written by the function under test. * The capture allocator stamps every fresh allocation with 0xCC so that * after the call: * - present ForceZero -> [0x00 * ySz] [0xCC * (MAX - ySz)] * - removed ForceZero -> [priv-key * ySz] [0xCC * (MAX - ySz)] * The check requires a captured MAX_KEX_KEY_SZ buffer whose bytes are all * either 0x00 or 0xCC AND that contains at least one 0x00. The DH private * key emitted by wc_DhGenerateKeyPair is overwhelmingly unlikely to be * entirely composed of 0x00 / 0xCC bytes, so this catches the mutation * while staying deterministic regardless of underlying malloc state. * * The peer value ssh->handshake->e is left zero, so the new * wc_DhCheckPubKey now fails between wc_DhGenerateKeyPair and wc_DhAgree; * the ForceZero on y_ptr is unconditional and still runs, so the buffer * assertion above is unaffected. */ static int test_KeyAgreeDh_server_zeroesEphemeralPrivKey(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte f[MAX_KEX_KEY_SZ]; word32 fSz = (word32)sizeof(f); int result = 0; CapturedBuf* c; int foundYPtr = 0; int captureInstalled = 0; wolfSSL_Malloc_cb prevMf = NULL; wolfSSL_Free_cb prevFf = NULL; wolfSSL_Realloc_cb prevRf = NULL; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -720; ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -721; goto out; } if (ssh->handshake == NULL) { result = -722; goto out; } #ifndef WOLFSSH_NO_DH_GROUP14_SHA256 ssh->handshake->kexId = ID_DH_GROUP14_SHA256; #elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512) ssh->handshake->kexId = ID_DH_GROUP16_SHA512; #elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1) ssh->handshake->kexId = ID_DH_GROUP14_SHA1; #elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1) ssh->handshake->kexId = ID_DH_GROUP1_SHA1; #else /* No fixed DH group enabled; nothing to exercise. */ result = -725; goto out; #endif wolfSSL_GetAllocators(&prevMf, &prevFf, &prevRf); /* Allocators unchanged on failure; nothing to restore. */ if (wolfSSL_SetAllocators(CaptureMalloc, CaptureFree, CaptureRealloc) != 0) { result = -723; goto out; } captureInstalled = 1; (void)wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz); wolfSSL_SetAllocators(prevMf, prevFf, prevRf); captureInstalled = 0; for (c = capturedFrees; c != NULL; c = c->next) { const byte* bytes; word32 i; int hasZero = 0; int hasOther = 0; if (c->size != MAX_KEX_KEY_SZ) continue; bytes = (const byte*)c->ptr; for (i = 0; i < MAX_KEX_KEY_SZ; i++) { if (bytes[i] == 0x00) { hasZero = 1; } else if (bytes[i] != 0xCC) { hasOther = 1; break; } } if (hasZero && !hasOther) { foundYPtr = 1; break; } } if (!foundYPtr) { result = -724; } out: if (captureInstalled) wolfSSL_SetAllocators(prevMf, prevFf, prevRf); DrainCaptured(); if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #endif /* WOLFSSH_SMALL_STACK && WOLFSSH_TEST_CAPTURING_ALLOCATOR */ /* Verify KeyAgreeDh_server rejects an out-of-range peer public value. * The server hook loads the SSH prime group from kexId itself, so feeding * ssh->handshake->e a value of 0 or 1 (outside [2, p-2]) must make * wc_DhCheckPubKey fail and the call return non-WS_SUCCESS instead of * deriving a known shared secret. On a wolfSSL whose wc_DhAgree already * validates the peer key this is an equivalent guard; its value is * defense-in-depth for builds whose wc_DhAgree does not validate. */ static int test_KeyAgreeDh_server_rejectsOutOfRangePeer(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; HandshakeInfo* hs = NULL; byte f[MAX_KEX_KEY_SZ]; word32 fSz; word32 c; int result = 0; static const byte badVals[2] = { 0x00, 0x01 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -740; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -741; } hs = ssh->handshake; if (hs == NULL) { result = -742; goto cleanup; } #ifndef WOLFSSH_NO_DH_GROUP14_SHA256 hs->kexId = ID_DH_GROUP14_SHA256; #elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512) hs->kexId = ID_DH_GROUP16_SHA512; #elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1) hs->kexId = ID_DH_GROUP14_SHA1; #elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1) hs->kexId = ID_DH_GROUP1_SHA1; #else /* No fixed DH group enabled; fail rather than silently pass. */ result = -744; goto cleanup; #endif for (c = 0; c < (word32)sizeof(badVals); c++) { int ret; hs->e[0] = badVals[c]; hs->eSz = 1; fSz = (word32)sizeof(f); ret = wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz); if (ret == WS_SUCCESS) { result = -743; break; } } cleanup: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #ifdef HAVE_FFDHE_2048 /* Verify KeyAgreeDh_client rejects an out-of-range peer public value. * A real prime group is loaded into ssh->handshake->privKey.dh so that an * f of 0 or 1 (outside [2, p-2]) is caught by wc_DhCheckPubKey before key * agreement, yielding WS_CRYPTO_FAILED rather than a known shared secret. * Equivalent-mutant caveat as in the server case above: it is a regression * guard for builds whose wc_DhAgree does not validate the peer key. */ static int test_KeyAgreeDh_client_rejectsOutOfRangePeer(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; HandshakeInfo* hs = NULL; byte badF[1]; word32 c; int result = 0; static const byte badVals[2] = { 0x00, 0x01 }; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -750; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -751; } hs = ssh->handshake; if (hs == NULL) { result = -752; goto cleanup; } for (c = 0; c < (word32)sizeof(badVals); c++) { int ret; /* The hook frees privKey.dh on return, so re-init each iteration. */ if (wc_InitDhKey(&hs->privKey.dh) != 0) { result = -753; break; } if (wc_DhSetNamedKey(&hs->privKey.dh, WC_FFDHE_2048) != 0) { wc_FreeDhKey(&hs->privKey.dh); result = -754; break; } /* x is unused on the reject path but ForceZero still runs over it. */ hs->xSz = 0; badF[0] = badVals[c]; ret = wolfSSH_TestKeyAgreeDh_client(ssh, WC_HASH_TYPE_SHA256, badF, (word32)sizeof(badF)); if (ret != WS_CRYPTO_FAILED) { result = -755; break; } } cleanup: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } #endif /* HAVE_FFDHE_2048 */ #endif /* !WOLFSSH_NO_DH */ #if !defined(WOLFSSH_NO_ECDH) && !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) /* Verify KeyAgreeEcdh_server rejects an off-curve peer ECC point. The peer * point is read from ssh->handshake->e as an X9.63 uncompressed point; (1, 1) * is a well-formed encoding that is not on P-256. In builds without * WOLFSSL_VALIDATE_ECC_IMPORT (the common embedded case this hardening targets) * wc_ecc_import_x963 accepts the coordinates, so the wc_ecc_check_key call in * the shared EccCheckPeerKey helper is what rejects it before * wc_ecc_shared_secret is reached; with WOLFSSL_VALIDATE_ECC_IMPORT the import * itself rejects it. Either way the function must fail. P-256 has cofactor 1, * so an off-curve point (not a wrong-subgroup one) is the meaningful test. */ static int test_KeyAgreeEcdh_server_rejectsOffCurvePoint(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte f[MAX_KEX_KEY_SZ]; word32 fSz = (word32)sizeof(f); int result = 0; /* X9.63 uncompressed point for P-256: 0x04 || X(32) || Y(32), here the * off-curve point (X=1, Y=1). */ byte point[1 + 32 + 32]; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -740; ssh = wolfSSH_new(ctx); if (ssh == NULL || ssh->handshake == NULL) { result = -741; goto out; } ssh->handshake->kexId = ID_ECDH_SHA2_NISTP256; WMEMSET(point, 0, sizeof(point)); point[0] = 0x04; /* uncompressed */ point[32] = 0x01; /* X = 1 (big-endian, last byte) */ point[64] = 0x01; /* Y = 1 */ WMEMCPY(ssh->handshake->e, point, sizeof(point)); ssh->handshake->eSz = (word32)sizeof(point); if (wolfSSH_TestKeyAgreeEcdh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz) == 0) { /* The off-curve peer point must not be accepted. */ result = -742; } out: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } /* Verify KeyAgreeEcdh_client rejects an off-curve peer ECC point. The peer * point 'f' is an X9.63 uncompressed encoding; (1, 1) is well-formed but is * not on P-256. In builds without WOLFSSL_VALIDATE_ECC_IMPORT (the common * embedded case this hardening targets) wc_ecc_import_x963 accepts the * coordinates, so the wc_ecc_check_key call in the shared EccCheckPeerKey * helper is what rejects it before wc_ecc_shared_secret is reached; with * WOLFSSL_VALIDATE_ECC_IMPORT the import itself rejects it. Either way the * function must fail. P-256 has cofactor 1, so an off-curve point (not a * wrong-subgroup one) is the meaningful test. */ static int test_KeyAgreeEcdh_client_rejectsOffCurvePoint(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int result = 0; /* X9.63 uncompressed point for P-256: 0x04 || X(32) || Y(32), here the * off-curve point (X=1, Y=1). */ byte point[1 + 32 + 32]; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -770; ssh = wolfSSH_new(ctx); if (ssh == NULL || ssh->handshake == NULL) { result = -771; goto out; } ssh->handshake->kexId = ID_ECDH_SHA2_NISTP256; /* Generate a real client ephemeral P-256 key, as SendKexDhInit() does * before KeyAgreeEcdh_client() runs. With a live private key in place the * only way the call can fail is the peer point validation, so the test * actually exercises the wc_ecc_check_key guard rather than tripping over a * missing private key. KeyAgreeEcdh_client() frees privKey.ecc on the way * out, so the cleanup path stays well defined. */ if (wc_ecc_init(&ssh->handshake->privKey.ecc) != 0) { result = -772; goto out; } ssh->handshake->useEcdh = 1; #ifdef HAVE_WC_ECC_SET_RNG if (wc_ecc_set_rng(&ssh->handshake->privKey.ecc, ssh->rng) != 0) { result = -774; goto out; } #endif if (wc_ecc_make_key_ex(ssh->rng, 32, &ssh->handshake->privKey.ecc, ECC_SECP256R1) != 0) { result = -775; goto out; } WMEMSET(point, 0, sizeof(point)); point[0] = 0x04; /* uncompressed */ point[32] = 0x01; /* X = 1 (big-endian, last byte) */ point[64] = 0x01; /* Y = 1 */ if (wolfSSH_TestKeyAgreeEcdh_client(ssh, WC_HASH_TYPE_SHA256, point, (word32)sizeof(point)) == 0) { /* The off-curve peer point must not be accepted. */ result = -773; } out: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); return result; } #endif /* !WOLFSSH_NO_ECDH && !WOLFSSH_NO_ECDH_SHA2_NISTP256 */ #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \ !defined(NO_FILESYSTEM) && !defined(WOLFSSL_NUCLEUS) && \ !defined(_WIN32) && !defined(WOLFSSH_ZEPHYR) #ifndef PATH_MAX #define PATH_MAX 4096 #endif static int scpTestSnprintfOk(int n, size_t bufSz) { return (n >= 0 && (size_t)n < bufSz); } static int pathsMatch(const char* a, const char* b) { char* aResolved = realpath(a, NULL); char* bResolved = realpath(b, NULL); int match = 0; if (aResolved != NULL && bResolved != NULL) match = (WSTRCMP(aResolved, bResolved) == 0); free(aResolved); free(bResolved); return match; } static int test_ScpRecvCallback_EndDirDepthGuard(void) { char tmpDir[] = "/tmp/wolfssh_scpXXXXXX"; char basePathRaw[PATH_MAX]; char evilPath[PATH_MAX]; char evilFileInBase[PATH_MAX]; char subPath[PATH_MAX]; char cwd[PATH_MAX]; char origCwd[PATH_MAX]; char* basePath = NULL; char* tmpResolved = NULL; struct stat st; WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int pathsReady = 0; int baseMkdirDone = 0; int subdirCreated = 0; int origCwdSaved = 0; int ret; int result = 0; basePathRaw[0] = '\0'; evilPath[0] = '\0'; evilFileInBase[0] = '\0'; subPath[0] = '\0'; if (getcwd(origCwd, sizeof(origCwd)) == NULL) return -799; origCwdSaved = 1; if (mkdtemp(tmpDir) == NULL) return -800; ret = snprintf(basePathRaw, sizeof(basePathRaw), "%s/scp_target", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(basePathRaw))) { result = -801; goto cleanup; } if (mkdir(basePathRaw, 0755) != 0) { result = -802; goto cleanup; } baseMkdirDone = 1; basePath = realpath(basePathRaw, NULL); tmpResolved = realpath(tmpDir, NULL); if (basePath == NULL || tmpResolved == NULL) { result = -803; goto cleanup; } ret = snprintf(evilPath, sizeof(evilPath), "%s/EVIL_FILE.txt", tmpResolved); if (!scpTestSnprintfOk(ret, sizeof(evilPath))) { result = -804; goto cleanup; } ret = snprintf(evilFileInBase, sizeof(evilFileInBase), "%s/EVIL_FILE.txt", basePath); if (!scpTestSnprintfOk(ret, sizeof(evilFileInBase))) { result = -804; goto cleanup; } ret = snprintf(subPath, sizeof(subPath), "%s/subdir", basePath); if (!scpTestSnprintfOk(ret, sizeof(subPath))) { result = -804; goto cleanup; } pathsReady = 1; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -805; goto cleanup; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -806; goto cleanup; } ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_REQUEST, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -807; goto cleanup; } ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_END_DIR, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_ABORT) { result = -808; goto cleanup; } if (ssh->scpDirDepth != 0) { result = -809; goto cleanup; } ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_FILE, basePath, "EVIL_FILE.txt", 0644, 0, 0, 0, NULL, 0, 0, NULL); if (ret == WS_SCP_CONTINUE) { (void)wsScpRecvCallback(ssh, WOLFSSH_SCP_FILE_DONE, basePath, "EVIL_FILE.txt", 0644, 0, 0, 0, NULL, 0, 0, wolfSSH_GetScpRecvCtx(ssh)); } if (stat(evilPath, &st) == 0) { (void)remove(evilPath); result = -810; goto cleanup; } if (getcwd(cwd, sizeof(cwd)) == NULL || !pathsMatch(cwd, basePath)) { result = -811; goto cleanup; } ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_DIR, basePath, "subdir", 0755, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -812; goto cleanup; } subdirCreated = 1; ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_END_DIR, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE || ssh->scpDirDepth != 0) { result = -813; goto cleanup; } if (getcwd(cwd, sizeof(cwd)) == NULL || !pathsMatch(cwd, basePath)) { result = -814; goto cleanup; } cleanup: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); free(basePath); free(tmpResolved); if (pathsReady) { (void)remove(evilPath); (void)remove(evilFileInBase); } if (subdirCreated) (void)rmdir(subPath); if (baseMkdirDone) (void)rmdir(basePathRaw); (void)rmdir(tmpDir); if (origCwdSaved && chdir(origCwd) != 0 && result == 0) result = -815; return result; } static int test_ScpRecvCallback_NewDirChdirFail(void) { char tmpDir[] = "/tmp/wolfssh_scpXXXXXX"; char basePathRaw[PATH_MAX]; char noexecSubPath[PATH_MAX]; char origCwd[PATH_MAX]; char* basePath = NULL; WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int baseMkdirDone = 0; int noexecCreated = 0; int origCwdSaved = 0; int ret; int result = 0; basePathRaw[0] = '\0'; noexecSubPath[0] = '\0'; if (getcwd(origCwd, sizeof(origCwd)) == NULL) return -820; origCwdSaved = 1; if (mkdtemp(tmpDir) == NULL) return -821; ret = snprintf(basePathRaw, sizeof(basePathRaw), "%s/scp_target", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(basePathRaw))) { result = -822; goto cleanup; } if (mkdir(basePathRaw, 0755) != 0) { result = -823; goto cleanup; } baseMkdirDone = 1; basePath = realpath(basePathRaw, NULL); if (basePath == NULL) { result = -824; goto cleanup; } ret = snprintf(noexecSubPath, sizeof(noexecSubPath), "%s/noexec_sub", basePath); if (!scpTestSnprintfOk(ret, sizeof(noexecSubPath))) { result = -825; goto cleanup; } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -826; goto cleanup; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -827; goto cleanup; } ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_REQUEST, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -828; goto cleanup; } /* pre-create noexec_sub with mode 0000 so WCHDIR fails after WMKDIR * gets EEXIST and continues */ if (mkdir(noexecSubPath, 0000) != 0) { result = -829; goto cleanup; } noexecCreated = 1; /* root bypasses directory permission checks; skip the wchdir-fail * sub-test to avoid a false failure */ if (geteuid() == 0) goto cleanup; ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_DIR, basePath, "noexec_sub", 0755, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_ABORT) { result = -830; goto cleanup; } if (ssh->scpDirDepth != 0) { result = -831; goto cleanup; } cleanup: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); free(basePath); if (noexecCreated) { (void)chmod(noexecSubPath, 0755); (void)rmdir(noexecSubPath); } if (baseMkdirDone) (void)rmdir(basePathRaw); (void)rmdir(tmpDir); if (origCwdSaved && chdir(origCwd) != 0 && result == 0) result = -832; return result; } /* A pre-existing symlink in the destination directory must not be followed * out of that directory, neither when entering it as a directory nor when * opening it as a file. */ static int test_ScpRecvCallback_SymlinkGuard(void) { #ifndef WOLFSSH_HAVE_SYMLINK /* symlink rejection is compiled out on this configuration */ return 0; #else char tmpDir[] = "/tmp/wolfssh_scpXXXXXX"; char basePathRaw[PATH_MAX]; char outsidePath[PATH_MAX]; char linkDirPath[PATH_MAX]; char linkFilePath[PATH_MAX]; char leakedPath[PATH_MAX]; char origCwd[PATH_MAX]; char* basePath = NULL; struct stat st; WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; int baseMkdirDone = 0; int outsideMkdirDone = 0; int linkDirDone = 0; int linkFileDone = 0; int origCwdSaved = 0; int ret; int result = 0; basePathRaw[0] = '\0'; outsidePath[0] = '\0'; linkDirPath[0] = '\0'; linkFilePath[0] = '\0'; leakedPath[0] = '\0'; if (getcwd(origCwd, sizeof(origCwd)) == NULL) return -840; origCwdSaved = 1; if (mkdtemp(tmpDir) == NULL) return -841; ret = snprintf(basePathRaw, sizeof(basePathRaw), "%s/scp_target", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(basePathRaw))) { result = -842; goto cleanup; } if (mkdir(basePathRaw, 0755) != 0) { result = -843; goto cleanup; } baseMkdirDone = 1; ret = snprintf(outsidePath, sizeof(outsidePath), "%s/outside", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(outsidePath))) { result = -844; goto cleanup; } if (mkdir(outsidePath, 0755) != 0) { result = -845; goto cleanup; } outsideMkdirDone = 1; basePath = realpath(basePathRaw, NULL); if (basePath == NULL) { result = -846; goto cleanup; } ret = snprintf(linkDirPath, sizeof(linkDirPath), "%s/linkdir", basePath); if (!scpTestSnprintfOk(ret, sizeof(linkDirPath))) { result = -847; goto cleanup; } ret = snprintf(linkFilePath, sizeof(linkFilePath), "%s/linkfile", basePath); if (!scpTestSnprintfOk(ret, sizeof(linkFilePath))) { result = -858; goto cleanup; } ret = snprintf(leakedPath, sizeof(leakedPath), "%s/leaked.txt", outsidePath); if (!scpTestSnprintfOk(ret, sizeof(leakedPath))) { result = -859; goto cleanup; } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -848; goto cleanup; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -849; goto cleanup; } /* NEW_REQUEST changes the working directory into basePath */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_REQUEST, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -850; goto cleanup; } /* plant a directory symlink pointing outside basePath */ if (symlink(outsidePath, linkDirPath) != 0) { result = -851; goto cleanup; } linkDirDone = 1; /* WMKDIR returns EEXIST for the existing symlink; the callback must * refuse to chdir through it rather than escape basePath */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_DIR, basePath, "linkdir", 0755, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_ABORT) { result = -852; goto cleanup; } if (ssh->scpDirDepth != 0) { result = -853; goto cleanup; } /* plant a (dangling) file symlink pointing outside basePath */ if (symlink(leakedPath, linkFilePath) != 0) { result = -854; goto cleanup; } linkFileDone = 1; /* the callback must refuse to open the symlink rather than write through * it to the outside target */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_FILE, basePath, "linkfile", 0644, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_ABORT) { result = -855; goto cleanup; } if (stat(leakedPath, &st) == 0) { (void)remove(leakedPath); result = -856; goto cleanup; } cleanup: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); free(basePath); /* NEW_REQUEST changed the process CWD into basePath, so leave it before * removing the created directories or the rmdir calls would fail */ if (origCwdSaved && chdir(origCwd) != 0 && result == 0) result = -857; if (linkDirDone) (void)remove(linkDirPath); if (linkFileDone) (void)remove(linkFilePath); if (outsideMkdirDone) (void)rmdir(outsidePath); if (baseMkdirDone) (void)rmdir(basePathRaw); (void)rmdir(tmpDir); return result; #endif /* WOLFSSH_HAVE_SYMLINK */ } /* Drive the default SCP receive callback through a full single-file receive * and confirm the peer-supplied modification/access times end up on the * written file. * * Which code path applies the timestamp is decided at build time, not by this * test: when futimens is detected (HAVE_FUTIMENS) the callback sets it on the * open descriptor before the closing flush, otherwise it sets it by path after * close. Both must yield the peer-supplied times, which is what this end-to-end * test asserts. When the descriptor path is compiled in, this also guards its * ordering relative to the closing flush: applying the timestamp before the * buffered data was flushed would leave the modification time at the current * time rather than the peer value. */ static int test_ScpRecvCallback_Timestamp(void) { char tmpDir[] = "/tmp/wolfssh_scptsXXXXXX"; char filePath[PATH_MAX]; char origCwd[PATH_MAX]; const char data[] = "wolfssh scp timestamp regression\n"; const word64 mTime = 1234567890; /* 2009-02-13 23:31:30 UTC */ const word64 aTime = 1000000000; /* 2001-09-09 01:46:40 UTC */ struct stat st; WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; char* basePath = NULL; int origCwdSaved = 0; int baseReady = 0; int ret; int result = 0; filePath[0] = '\0'; if (getcwd(origCwd, sizeof(origCwd)) == NULL) return -850; origCwdSaved = 1; if (mkdtemp(tmpDir) == NULL) return -851; baseReady = 1; basePath = realpath(tmpDir, NULL); if (basePath == NULL) { result = -852; goto cleanup; } ret = snprintf(filePath, sizeof(filePath), "%s/ts_file.txt", basePath); if (!scpTestSnprintfOk(ret, sizeof(filePath))) { result = -853; goto cleanup; } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) { result = -854; goto cleanup; } ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -855; goto cleanup; } /* enter the destination directory */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_REQUEST, basePath, NULL, 0, 0, 0, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -856; goto cleanup; } /* open the destination file */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_NEW_FILE, basePath, "ts_file.txt", 0644, mTime, aTime, sizeof(data) - 1, NULL, 0, 0, NULL); if (ret != WS_SCP_CONTINUE) { result = -857; goto cleanup; } /* write the file contents */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_FILE_PART, basePath, "ts_file.txt", 0644, mTime, aTime, sizeof(data) - 1, (byte*)data, sizeof(data) - 1, 0, wolfSSH_GetScpRecvCtx(ssh)); if (ret != WS_SCP_CONTINUE) { result = -858; goto cleanup; } /* close the file and apply the peer-supplied timestamps */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_FILE_DONE, basePath, "ts_file.txt", 0644, mTime, aTime, sizeof(data) - 1, NULL, 0, 0, wolfSSH_GetScpRecvCtx(ssh)); if (ret != WS_SCP_CONTINUE) { result = -859; goto cleanup; } /* the written file must carry the peer-supplied timestamps */ if (stat(filePath, &st) != 0) { result = -860; goto cleanup; } if ((word64)st.st_mtime != mTime) { result = -861; goto cleanup; } if ((word64)st.st_atime != aTime) { result = -862; goto cleanup; } #ifdef HAVE_FUTIMENS /* Descriptor build only: a FILE_DONE carrying timestamps but no open file * (NULL ctx) must abort rather than silently fall back to a path-based * update that could follow a swapped symlink. Use distinct times and * confirm the existing file's modification time is left untouched. */ ret = wsScpRecvCallback(ssh, WOLFSSH_SCP_FILE_DONE, basePath, "ts_file.txt", 0644, mTime + 100, aTime + 100, 0, NULL, 0, 0, NULL); if (ret != WS_SCP_ABORT) { result = -864; goto cleanup; } if (stat(filePath, &st) != 0) { result = -865; goto cleanup; } if ((word64)st.st_mtime != mTime) { result = -866; goto cleanup; } #endif cleanup: if (ssh != NULL) wolfSSH_free(ssh); if (ctx != NULL) wolfSSH_CTX_free(ctx); if (filePath[0] != '\0') (void)remove(filePath); free(basePath); if (origCwdSaved && chdir(origCwd) != 0 && result == 0) result = -863; if (baseReady) (void)rmdir(tmpDir); return result; } #if defined(HAVE_UTIMENSAT) && defined(WOLFSSH_HAVE_SYMLINK) /* Exercise the no-follow path fallback (no descriptor-based call available). * Setting times through a symlink must land on the symlink itself, not the * target, so a swapped symlink cannot redirect a peer-supplied timestamp. */ static int test_ScpTimestamp_NoFollow(void) { char tmpDir[] = "/tmp/wolfssh_scpnfXXXXXX"; char canaryPath[PATH_MAX]; char linkPath[PATH_MAX]; const word64 mTime = 1222333444; /* distinct from the canary's own time */ const word64 aTime = 1100000000; struct timeval tv[2]; struct stat st; time_t canaryOrig; int baseReady = 0; int canaryReady = 0; int linkReady = 0; int ret; int result = 0; int fd; canaryPath[0] = '\0'; linkPath[0] = '\0'; if (mkdtemp(tmpDir) == NULL) return -870; baseReady = 1; ret = snprintf(canaryPath, sizeof(canaryPath), "%s/canary.txt", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(canaryPath))) { result = -871; goto cleanup; } ret = snprintf(linkPath, sizeof(linkPath), "%s/link", tmpDir); if (!scpTestSnprintfOk(ret, sizeof(linkPath))) { result = -872; goto cleanup; } fd = open(canaryPath, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) { result = -873; goto cleanup; } canaryReady = 1; if (write(fd, "canary\n", 7) != 7) { close(fd); result = -881; goto cleanup; } close(fd); if (stat(canaryPath, &st) != 0) { result = -874; goto cleanup; } canaryOrig = st.st_mtime; if (symlink(canaryPath, linkPath) != 0) { result = -875; goto cleanup; } linkReady = 1; tv[0].tv_sec = (time_t)aTime; tv[0].tv_usec = 0; tv[1].tv_sec = (time_t)mTime; tv[1].tv_usec = 0; if (WUTIMES_NOFOLLOW(linkPath, tv) != 0) { result = -876; goto cleanup; } /* the symlink's own modification time must carry the supplied value */ if (lstat(linkPath, &st) != 0) { result = -877; goto cleanup; } if ((word64)st.st_mtime != mTime) { result = -878; goto cleanup; } /* the target the symlink points at must be left untouched */ if (stat(canaryPath, &st) != 0) { result = -879; goto cleanup; } if (st.st_mtime != canaryOrig) { result = -880; goto cleanup; } cleanup: if (linkReady) (void)remove(linkPath); if (canaryReady) (void)remove(canaryPath); if (baseReady) (void)rmdir(tmpDir); return result; } #endif /* HAVE_UTIMENSAT && WOLFSSH_HAVE_SYMLINK */ #endif /* WOLFSSH_SCP recv callback depth guard test */ /* ParseECCPubKey() Unit Test */ #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 /* The payload of keys/gretel-key-ecc.pub: * string "ecdsa-sha2-nistp256" | string "nistp256" | string Q * ParseECCPubKey() must reject blobs whose algorithm name or curve name * does not match the negotiated host key algorithm. A MitM must not be * able to choose a different curve by lying in the blob. */ static const byte eccPubKeyBlob[] = { 0x00, 0x00, 0x00, 0x13, 0x65, 0x63, 0x64, 0x73, 0x61, 0x2D, 0x73, 0x68, 0x61, 0x32, 0x2D, 0x6E, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x00, 0x00, 0x00, 0x08, 0x6E, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x00, 0x00, 0x00, 0x41, 0x04, 0xA0, 0x2D, 0x1F, 0xC7, 0x2A, 0x68, 0x36, 0xED, 0x24, 0x58, 0xED, 0xBE, 0x22, 0xE8, 0x6C, 0x70, 0x66, 0x8C, 0x2B, 0x46, 0xE7, 0xA0, 0xCC, 0x90, 0xFE, 0x80, 0xE0, 0xCD, 0x87, 0xF7, 0x35, 0xF6, 0xFD, 0x80, 0xA0, 0xD6, 0x1F, 0x5B, 0x61, 0x2E, 0xD6, 0x1D, 0xDF, 0x54, 0x40, 0x3C, 0x17, 0x3B, 0x51, 0xE1, 0x21, 0x9C, 0xD1, 0x61, 0xE7, 0x17, 0x87, 0xB4, 0x86, 0xF4, 0xFE, 0x06, 0x85, 0x16, }; /* Offsets of interest in eccPubKeyBlob. */ #define ECC_BLOB_ALGO_DIGITS 20 /* the "256" in "ecdsa-sha2-nistp256" */ #define ECC_BLOB_CURVE_DIGITS 32 /* the "256" in "nistp256" */ #define ECC_BLOB_POINT 39 /* leading byte (0x04) of Q */ #define ECC_BLOB_TRUNC_SZ 30 /* cuts the blob mid curve name */ static const byte eccBadPointFormat[] = { 0x05 }; typedef struct { const char* name; word32 patchIdx; const byte* patch; word32 patchSz; /* 0 = no patch */ word32 blobSz; byte pubKeyId; /* negotiated host key algorithm */ int expected; } ParseECCPubKeyTestVector; static const ParseECCPubKeyTestVector parseECCPubKeyTestVectors[] = { { "valid nistp256 blob", 0, NULL, 0, sizeof(eccPubKeyBlob), ID_ECDSA_SHA2_NISTP256, WS_SUCCESS }, { "algo name mismatch", ECC_BLOB_ALGO_DIGITS, (const byte*)"384", 3, sizeof(eccPubKeyBlob), ID_ECDSA_SHA2_NISTP256, WS_INVALID_ALGO_ID }, #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384 { "blob downgrades negotiated nistp384", 0, NULL, 0, sizeof(eccPubKeyBlob), ID_ECDSA_SHA2_NISTP384, WS_INVALID_ALGO_ID }, #endif { "curve name mismatch", ECC_BLOB_CURVE_DIGITS, (const byte*)"384", 3, sizeof(eccPubKeyBlob), ID_ECDSA_SHA2_NISTP256, WS_INVALID_PRIME_CURVE }, { "corrupt point format", ECC_BLOB_POINT, eccBadPointFormat, 1, sizeof(eccPubKeyBlob), ID_ECDSA_SHA2_NISTP256, WS_ECC_E }, { "truncated blob", 0, NULL, 0, ECC_BLOB_TRUNC_SZ, ID_ECDSA_SHA2_NISTP256, WS_BUFFER_E }, }; static int test_ParseECCPubKey(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; const ParseECCPubKeyTestVector* tv; int tc = (int)(sizeof(parseECCPubKeyTestVectors) / sizeof(parseECCPubKeyTestVectors[0])); int i; int ret; int failures = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return 1; ssh = wolfSSH_new(ctx); if (ssh == NULL || ssh->handshake == NULL) { wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return 1; } for (i = 0, tv = parseECCPubKeyTestVectors; i < tc; i++, tv++) { byte blob[sizeof(eccPubKeyBlob)]; WMEMCPY(blob, eccPubKeyBlob, sizeof(eccPubKeyBlob)); if (tv->patchSz > 0) WMEMCPY(blob + tv->patchIdx, tv->patch, tv->patchSz); ssh->handshake->pubKeyId = tv->pubKeyId; ret = wolfSSH_TestParseECCPubKey(ssh, blob, tv->blobSz); if (ret != tv->expected) { fprintf(stderr, "\t[%d] \"%s\" FAIL: got %d, expected %d\n", i, tv->name, ret, tv->expected); failures++; } } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return failures; } #endif /* !WOLFSSH_NO_ECDSA_SHA2_NISTP256 */ /* DoUserAuthRequestRsa() Unit Test */ #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_SSH_RSA_SHA1) /* RFC 4253 Section 6.6: the RSA signature blob is a string of raw * signature bytes, not an mpint. The fixed signature below was made * with keys/hansel-key-rsa.pem over the message * "wolfSSH unit test message 1" (SHA-1, PKCS#1 v1.5) and its first * byte (0xB7) has the high bit set. An mpint parse would reject it * as negative, so this pins the string parse. */ /* string "ssh-rsa" | mpint e | mpint n, from keys/hansel-key-rsa.pem */ static const byte userAuthRsaPubKeyBlob[] = { 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2D, 0x72, 0x73, 0x61, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0xBD, 0x3F, 0x76, 0x45, 0xA3, 0x03, 0xAC, 0x38, 0xD5, 0xC7, 0x0F, 0x93, 0x30, 0x5A, 0x20, 0x9C, 0x89, 0x7C, 0xAD, 0x05, 0x16, 0x46, 0x86, 0x83, 0x0D, 0x8A, 0x2B, 0x16, 0x4A, 0x05, 0x2C, 0xE4, 0x77, 0x47, 0x70, 0x00, 0xAE, 0x1D, 0x83, 0xE2, 0xD9, 0x6E, 0x99, 0xD4, 0xF0, 0x45, 0x98, 0x15, 0x93, 0xF6, 0x87, 0x4E, 0xAC, 0x64, 0x63, 0xA1, 0x95, 0xC9, 0x7C, 0x30, 0xE8, 0x3E, 0x2F, 0xA3, 0xF1, 0x24, 0x9F, 0x0C, 0x6B, 0x1C, 0xFE, 0x1B, 0x02, 0x99, 0xCD, 0xC6, 0xA7, 0x6C, 0x84, 0x85, 0x46, 0x54, 0x12, 0x40, 0xE1, 0xB4, 0xE5, 0xF2, 0xAA, 0x39, 0xEC, 0xD6, 0x27, 0x24, 0x0B, 0xD1, 0xA1, 0xE2, 0xEF, 0x34, 0x69, 0x25, 0x6D, 0xC0, 0x74, 0x67, 0x25, 0x98, 0x7D, 0xC4, 0xF8, 0x52, 0xAB, 0x9B, 0x4B, 0x3A, 0x12, 0x1D, 0xE1, 0xE3, 0xFA, 0xD6, 0xCF, 0x9A, 0xE6, 0x9C, 0x23, 0x4E, 0x39, 0xC4, 0x84, 0x16, 0x88, 0x3D, 0x42, 0x4E, 0xD8, 0x2F, 0xCC, 0xD2, 0x91, 0x67, 0x9D, 0xB6, 0x71, 0x2A, 0x02, 0x65, 0x5F, 0xBB, 0x75, 0x0E, 0x8C, 0xBB, 0x87, 0x97, 0x97, 0xC6, 0xF8, 0xB2, 0x98, 0xE2, 0x2F, 0x68, 0x26, 0x4A, 0x53, 0xEC, 0x79, 0x3A, 0x8A, 0x5F, 0xCC, 0xCF, 0xF0, 0x16, 0x47, 0xB2, 0xD0, 0x43, 0xD6, 0x36, 0x6C, 0xC8, 0xE7, 0x2F, 0xFE, 0xA7, 0x35, 0x39, 0x69, 0xFB, 0x1D, 0x78, 0x45, 0x9D, 0x89, 0x00, 0xC8, 0x41, 0xCF, 0x34, 0x1F, 0xA3, 0xF3, 0xF1, 0xFB, 0x28, 0x14, 0xFB, 0xD8, 0x48, 0x6F, 0xAC, 0xE3, 0xFC, 0x33, 0xD1, 0xDB, 0xAE, 0xEF, 0x27, 0x9E, 0x57, 0x56, 0x29, 0xA2, 0x1A, 0x3A, 0xE5, 0x9A, 0xFE, 0xA4, 0x49, 0xC8, 0x7F, 0xB7, 0x4E, 0xD0, 0x1F, 0x04, 0x6E, 0x58, 0x16, 0xB7, 0xEB, 0x9D, 0xF8, 0x92, 0x3C, 0xC2, 0xB0, 0x21, 0x7C, 0x4E, 0x31, }; /* SHA-1 of "wolfSSH unit test message 1" */ static const byte userAuthRsaDigest[] = { 0x2A, 0x43, 0xF5, 0x19, 0x09, 0xEE, 0x2D, 0x85, 0x89, 0xD3, 0xE0, 0xCE, 0xF8, 0xA6, 0x8A, 0xC4, 0xD3, 0x33, 0xB3, 0x30, }; /* string "ssh-rsa" | string signature */ static const byte userAuthRsaSigBlob[] = { 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2D, 0x72, 0x73, 0x61, 0x00, 0x00, 0x01, 0x00, 0xB7, 0xFD, 0xC3, 0x7B, 0x4A, 0xAD, 0x4B, 0x04, 0x28, 0xD0, 0xAA, 0x41, 0x59, 0x4D, 0xFB, 0x37, 0xBD, 0x2F, 0xA4, 0x93, 0x63, 0x4D, 0x10, 0xCF, 0x95, 0x59, 0x4C, 0x37, 0xBE, 0x71, 0xF1, 0x3D, 0xF5, 0x8A, 0x72, 0x92, 0x22, 0xE3, 0x0F, 0xE9, 0xAE, 0x12, 0xA9, 0xD3, 0xC8, 0x6A, 0x78, 0x66, 0x65, 0x4C, 0xDB, 0xA0, 0xB2, 0x8B, 0x19, 0x0F, 0x05, 0xC4, 0x05, 0x69, 0x54, 0x13, 0x34, 0x17, 0xB2, 0xEE, 0x77, 0x41, 0x9B, 0x17, 0xD6, 0x52, 0xA7, 0x1C, 0x81, 0x84, 0xED, 0x60, 0x3D, 0x52, 0xEF, 0x57, 0xCD, 0xE8, 0x9D, 0x51, 0xCB, 0x38, 0xC8, 0xB2, 0x8E, 0x74, 0x2F, 0xFD, 0x32, 0xCB, 0x0D, 0x8B, 0xFB, 0x7B, 0xCC, 0x35, 0xFF, 0x75, 0x10, 0x89, 0x0A, 0x1E, 0xA8, 0x37, 0xC9, 0x39, 0xED, 0x9F, 0xDA, 0x5D, 0xC5, 0x38, 0xEA, 0xC3, 0xBA, 0x58, 0x89, 0x5A, 0xA0, 0x84, 0x4D, 0x5F, 0x73, 0xF9, 0x5A, 0xC8, 0xD2, 0xEA, 0xB5, 0x6D, 0x3D, 0xC0, 0x12, 0xA7, 0x79, 0x30, 0x16, 0xE3, 0x2F, 0xBC, 0xAB, 0x12, 0xA8, 0xA1, 0xAB, 0x4B, 0xB3, 0x07, 0xF1, 0xDA, 0x1E, 0x3E, 0x5F, 0x02, 0x4B, 0x73, 0x22, 0x26, 0xC5, 0x51, 0xFB, 0xD1, 0x81, 0x53, 0x3B, 0xBA, 0x5E, 0x36, 0x2A, 0xBF, 0xC2, 0xB2, 0x9A, 0x0C, 0x8C, 0xB2, 0xCB, 0x6B, 0x9F, 0x30, 0xC8, 0x63, 0xA5, 0x72, 0xAF, 0x1D, 0x96, 0xE7, 0xB6, 0x17, 0xC4, 0xEB, 0x5F, 0xFD, 0xA4, 0xFB, 0xF8, 0xE4, 0x69, 0xE4, 0xA3, 0x47, 0x59, 0x2D, 0x8F, 0x4F, 0xB3, 0xD2, 0xAA, 0xD2, 0xF3, 0xCA, 0x42, 0xD5, 0xF7, 0x25, 0x5B, 0xCD, 0x60, 0x17, 0xA2, 0x0C, 0xE0, 0xF4, 0xEE, 0xE0, 0xF6, 0xED, 0x41, 0xC9, 0x00, 0x1B, 0x5A, 0x24, 0xD4, 0x18, 0xBA, 0xAC, 0x40, 0xBD, 0x7F, 0xFD, 0x46, 0x2F, 0xC5, 0x19, 0xF9, 0xE6, 0x2F, 0x16, }; /* Offsets of interest in userAuthRsaSigBlob. */ #define RSA_SIG_BLOB_ALGO 4 /* "ssh-rsa" */ #define RSA_SIG_BLOB_LEN 11 /* length of the signature string */ #define RSA_SIG_BLOB_SIG 15 /* first byte of the raw signature */ /* 257: one past the actual signature size */ static const byte userAuthRsaSigLenOverrun[] = { 0x00, 0x00, 0x01, 0x01 }; typedef struct { const char* name; word32 patchIdx; const byte* patch; word32 patchSz; /* 0 = no patch */ word32 flipIdx; /* XOR 0x01 into this index; 0 = none */ int expected; } UserAuthRsaTestVector; static const UserAuthRsaTestVector userAuthRsaTestVectors[] = { { "high bit signature accepted", 0, NULL, 0, 0, WS_SUCCESS }, { "corrupt signature rejected", 0, NULL, 0, RSA_SIG_BLOB_SIG + 128, WS_RSA_E }, { "signature length overrun", RSA_SIG_BLOB_LEN, userAuthRsaSigLenOverrun, 4, 0, WS_BUFFER_E }, { "signature algo name mismatch", RSA_SIG_BLOB_ALGO, (const byte*)"ssh-dss", 7, 0, WS_INVALID_ALGO_ID }, }; static int test_DoUserAuthRequestRsa(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; const UserAuthRsaTestVector* tv; int tc = (int)(sizeof(userAuthRsaTestVectors) / sizeof(userAuthRsaTestVectors[0])); int i; int ret; int failures = 0; /* The point of the test is a signature whose leading byte has the * high bit set. Guard against the vector being regenerated without * that property. */ if ((userAuthRsaSigBlob[RSA_SIG_BLOB_SIG] & 0x80) == 0) { fprintf(stderr, "\tuserAuthRsaSigBlob needs its high bit set\n"); return 1; } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return 1; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return 1; } for (i = 0, tv = userAuthRsaTestVectors; i < tc; i++, tv++) { byte sigBlob[sizeof(userAuthRsaSigBlob)]; byte digest[sizeof(userAuthRsaDigest)]; WS_UserAuthData_PublicKey pk; WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); if (tv->patchSz > 0) WMEMCPY(sigBlob + tv->patchIdx, tv->patch, tv->patchSz); if (tv->flipIdx > 0) sigBlob[tv->flipIdx] ^= 0x01; WMEMSET(&pk, 0, sizeof(pk)); pk.publicKeyType = (const byte*)"ssh-rsa"; pk.publicKeyTypeSz = 7; pk.publicKey = userAuthRsaPubKeyBlob; pk.publicKeySz = (word32)sizeof(userAuthRsaPubKeyBlob); pk.hasSignature = 1; pk.signature = sigBlob; pk.signatureSz = (word32)sizeof(sigBlob); ret = wolfSSH_TestDoUserAuthRequestRsa(ssh, &pk, WC_HASH_TYPE_SHA, digest, (word32)sizeof(digest)); if (ret != tv->expected) { fprintf(stderr, "\t[%d] \"%s\" FAIL: got %d, expected %d\n", i, tv->name, ret, tv->expected); failures++; } } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return failures; } static int test_ParseRSAPubKey(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; byte blob[sizeof(userAuthRsaPubKeyBlob)]; int ret; int failures = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return 1; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return 1; } WMEMCPY(blob, userAuthRsaPubKeyBlob, sizeof(blob)); /* valid blob */ ret = wolfSSH_TestParseRSAPubKey(ssh, blob, (word32)sizeof(blob)); if (ret != WS_SUCCESS) { fprintf(stderr, "\t\"valid\" FAIL: got %d, expected %d\n", ret, WS_SUCCESS); failures++; } /* truncated blob: fails after the key is initialized */ ret = wolfSSH_TestParseRSAPubKey(ssh, blob, (word32)(sizeof(blob) / 2)); if (ret != WS_RSA_E) { fprintf(stderr, "\t\"truncated\" FAIL: got %d, expected %d\n", ret, WS_RSA_E); failures++; } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return failures; } #ifdef WOLFSSH_CERTS /* DoUserAuthRequestRsaCert() parses the signature blob the same way as * DoUserAuthRequestRsa(), but takes the public key from an X.509 * certificate. This is a self-signed certificate made from * keys/hansel-key-rsa.pem, the same key as the vectors above, so the * digest and signature blob are reused. */ static const byte userAuthRsaCertDer[] = { 0x30, 0x82, 0x03, 0xFB, 0x30, 0x82, 0x02, 0xE3, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x1C, 0x71, 0x06, 0xEC, 0x89, 0xF3, 0x37, 0x6F, 0xE7, 0xBB, 0x3C, 0xE2, 0x74, 0x54, 0x43, 0x1F, 0x75, 0x0A, 0xED, 0xD5, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x81, 0x8C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0C, 0x02, 0x57, 0x41, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6C, 0x65, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0B, 0x77, 0x6F, 0x6C, 0x66, 0x53, 0x53, 0x4C, 0x20, 0x49, 0x6E, 0x63, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x0B, 0x44, 0x65, 0x76, 0x65, 0x6C, 0x6F, 0x70, 0x6D, 0x65, 0x6E, 0x74, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x06, 0x48, 0x61, 0x6E, 0x73, 0x65, 0x6C, 0x31, 0x21, 0x30, 0x1F, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, 0x16, 0x12, 0x68, 0x61, 0x6E, 0x73, 0x65, 0x6C, 0x40, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x30, 0x1E, 0x17, 0x0D, 0x32, 0x36, 0x30, 0x36, 0x31, 0x32, 0x31, 0x37, 0x34, 0x35, 0x30, 0x39, 0x5A, 0x17, 0x0D, 0x34, 0x38, 0x30, 0x35, 0x30, 0x37, 0x31, 0x37, 0x34, 0x35, 0x30, 0x39, 0x5A, 0x30, 0x81, 0x8C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0C, 0x02, 0x57, 0x41, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6C, 0x65, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0B, 0x77, 0x6F, 0x6C, 0x66, 0x53, 0x53, 0x4C, 0x20, 0x49, 0x6E, 0x63, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x0B, 0x44, 0x65, 0x76, 0x65, 0x6C, 0x6F, 0x70, 0x6D, 0x65, 0x6E, 0x74, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x06, 0x48, 0x61, 0x6E, 0x73, 0x65, 0x6C, 0x31, 0x21, 0x30, 0x1F, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01, 0x16, 0x12, 0x68, 0x61, 0x6E, 0x73, 0x65, 0x6C, 0x40, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00, 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xBD, 0x3F, 0x76, 0x45, 0xA3, 0x03, 0xAC, 0x38, 0xD5, 0xC7, 0x0F, 0x93, 0x30, 0x5A, 0x20, 0x9C, 0x89, 0x7C, 0xAD, 0x05, 0x16, 0x46, 0x86, 0x83, 0x0D, 0x8A, 0x2B, 0x16, 0x4A, 0x05, 0x2C, 0xE4, 0x77, 0x47, 0x70, 0x00, 0xAE, 0x1D, 0x83, 0xE2, 0xD9, 0x6E, 0x99, 0xD4, 0xF0, 0x45, 0x98, 0x15, 0x93, 0xF6, 0x87, 0x4E, 0xAC, 0x64, 0x63, 0xA1, 0x95, 0xC9, 0x7C, 0x30, 0xE8, 0x3E, 0x2F, 0xA3, 0xF1, 0x24, 0x9F, 0x0C, 0x6B, 0x1C, 0xFE, 0x1B, 0x02, 0x99, 0xCD, 0xC6, 0xA7, 0x6C, 0x84, 0x85, 0x46, 0x54, 0x12, 0x40, 0xE1, 0xB4, 0xE5, 0xF2, 0xAA, 0x39, 0xEC, 0xD6, 0x27, 0x24, 0x0B, 0xD1, 0xA1, 0xE2, 0xEF, 0x34, 0x69, 0x25, 0x6D, 0xC0, 0x74, 0x67, 0x25, 0x98, 0x7D, 0xC4, 0xF8, 0x52, 0xAB, 0x9B, 0x4B, 0x3A, 0x12, 0x1D, 0xE1, 0xE3, 0xFA, 0xD6, 0xCF, 0x9A, 0xE6, 0x9C, 0x23, 0x4E, 0x39, 0xC4, 0x84, 0x16, 0x88, 0x3D, 0x42, 0x4E, 0xD8, 0x2F, 0xCC, 0xD2, 0x91, 0x67, 0x9D, 0xB6, 0x71, 0x2A, 0x02, 0x65, 0x5F, 0xBB, 0x75, 0x0E, 0x8C, 0xBB, 0x87, 0x97, 0x97, 0xC6, 0xF8, 0xB2, 0x98, 0xE2, 0x2F, 0x68, 0x26, 0x4A, 0x53, 0xEC, 0x79, 0x3A, 0x8A, 0x5F, 0xCC, 0xCF, 0xF0, 0x16, 0x47, 0xB2, 0xD0, 0x43, 0xD6, 0x36, 0x6C, 0xC8, 0xE7, 0x2F, 0xFE, 0xA7, 0x35, 0x39, 0x69, 0xFB, 0x1D, 0x78, 0x45, 0x9D, 0x89, 0x00, 0xC8, 0x41, 0xCF, 0x34, 0x1F, 0xA3, 0xF3, 0xF1, 0xFB, 0x28, 0x14, 0xFB, 0xD8, 0x48, 0x6F, 0xAC, 0xE3, 0xFC, 0x33, 0xD1, 0xDB, 0xAE, 0xEF, 0x27, 0x9E, 0x57, 0x56, 0x29, 0xA2, 0x1A, 0x3A, 0xE5, 0x9A, 0xFE, 0xA4, 0x49, 0xC8, 0x7F, 0xB7, 0x4E, 0xD0, 0x1F, 0x04, 0x6E, 0x58, 0x16, 0xB7, 0xEB, 0x9D, 0xF8, 0x92, 0x3C, 0xC2, 0xB0, 0x21, 0x7C, 0x4E, 0x31, 0x02, 0x03, 0x01, 0x00, 0x01, 0xA3, 0x53, 0x30, 0x51, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0x67, 0x59, 0x9C, 0xD7, 0x16, 0x3F, 0xE6, 0x98, 0x47, 0x4F, 0xAE, 0x62, 0x4F, 0xAE, 0x27, 0x3A, 0xE8, 0xF6, 0x40, 0xAE, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x1D, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x67, 0x59, 0x9C, 0xD7, 0x16, 0x3F, 0xE6, 0x98, 0x47, 0x4F, 0xAE, 0x62, 0x4F, 0xAE, 0x27, 0x3A, 0xE8, 0xF6, 0x40, 0xAE, 0x30, 0x0F, 0x06, 0x03, 0x55, 0x1D, 0x13, 0x01, 0x01, 0xFF, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xFF, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x77, 0x70, 0xC5, 0x15, 0xFE, 0xF8, 0x2D, 0xCB, 0x5C, 0x18, 0x36, 0x87, 0xC9, 0x82, 0xFD, 0x6D, 0x3C, 0x78, 0xD8, 0xCF, 0xEB, 0x71, 0xFD, 0xEB, 0xD7, 0x05, 0x9A, 0xAE, 0xBE, 0xE1, 0x55, 0xCF, 0xE9, 0x04, 0xE3, 0xF1, 0xAE, 0x30, 0xDE, 0x19, 0xC3, 0x5E, 0x25, 0xDE, 0xB7, 0x39, 0x21, 0x57, 0x82, 0xD5, 0x98, 0xD6, 0x19, 0x49, 0x9D, 0x9F, 0xDE, 0x07, 0x9D, 0xEE, 0xB1, 0x24, 0xBD, 0x3E, 0xFD, 0xBA, 0x27, 0xA2, 0x9D, 0x0C, 0x08, 0x77, 0xA7, 0xB1, 0xFA, 0x6E, 0x36, 0xAA, 0xAE, 0x1F, 0xFA, 0xF6, 0xAE, 0x0A, 0x72, 0x48, 0x5C, 0x89, 0xD6, 0x4F, 0x10, 0x80, 0x3A, 0x2A, 0xA8, 0x6C, 0x00, 0x06, 0x41, 0x0B, 0xA6, 0xAA, 0x20, 0xE5, 0xEB, 0x38, 0xD4, 0xF7, 0x67, 0xCC, 0x40, 0x05, 0x61, 0xF9, 0x89, 0x8A, 0xF0, 0xCB, 0x03, 0xD9, 0x19, 0x8D, 0x63, 0x53, 0xB7, 0x2C, 0x53, 0x13, 0xA5, 0x3C, 0x5B, 0x5D, 0xDA, 0x20, 0x96, 0xDD, 0x7F, 0xF8, 0x63, 0x5A, 0x47, 0x59, 0x10, 0x48, 0xA7, 0x35, 0x84, 0xF2, 0x61, 0x4C, 0x3E, 0xAC, 0xE6, 0x06, 0x90, 0x96, 0x07, 0xA3, 0x7B, 0x2D, 0x36, 0xEF, 0x5D, 0xD0, 0x5C, 0x0A, 0x1C, 0x4D, 0xA2, 0x81, 0x18, 0xE3, 0x22, 0xFB, 0xBF, 0x9B, 0x12, 0x68, 0xAD, 0x6A, 0xCD, 0xF2, 0x72, 0xD3, 0xBA, 0x24, 0x63, 0xCC, 0x45, 0x47, 0xDE, 0x83, 0xD6, 0x8A, 0x94, 0xD2, 0xC1, 0xDC, 0xAB, 0xFD, 0x4E, 0xF0, 0x11, 0x21, 0xB9, 0x0F, 0xF3, 0xEE, 0x87, 0xF9, 0x03, 0x1A, 0xB4, 0x75, 0x21, 0x81, 0xDA, 0x2E, 0x1D, 0x82, 0x63, 0x26, 0x34, 0x56, 0x91, 0x2D, 0xDC, 0xFD, 0x0A, 0xF2, 0x3F, 0xBA, 0x44, 0xCD, 0x81, 0x53, 0x1F, 0x8B, 0xD3, 0x38, 0x22, 0x02, 0x05, 0x85, 0x35, 0x41, 0x50, 0xE8, 0x48, 0x6E, 0xE3, 0x7D, 0xA9, 0xFE, 0x5C, 0x39, }; static int test_DoUserAuthRequestRsaCert(void) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; const UserAuthRsaTestVector* tv; int tc = (int)(sizeof(userAuthRsaTestVectors) / sizeof(userAuthRsaTestVectors[0])); int i; int ret; int failures = 0; if ((userAuthRsaSigBlob[RSA_SIG_BLOB_SIG] & 0x80) == 0) { fprintf(stderr, "\tuserAuthRsaSigBlob needs its high bit set\n"); return 1; } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return 1; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return 1; } for (i = 0, tv = userAuthRsaTestVectors; i < tc; i++, tv++) { byte sigBlob[sizeof(userAuthRsaSigBlob)]; byte digest[sizeof(userAuthRsaDigest)]; WS_UserAuthData_PublicKey pk; WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); if (tv->patchSz > 0) WMEMCPY(sigBlob + tv->patchIdx, tv->patch, tv->patchSz); if (tv->flipIdx > 0) sigBlob[tv->flipIdx] ^= 0x01; WMEMSET(&pk, 0, sizeof(pk)); pk.publicKeyType = (const byte*)"x509v3-ssh-rsa"; pk.publicKeyTypeSz = 14; pk.publicKey = userAuthRsaCertDer; pk.publicKeySz = (word32)sizeof(userAuthRsaCertDer); pk.hasSignature = 1; pk.signature = sigBlob; pk.signatureSz = (word32)sizeof(sigBlob); ret = wolfSSH_TestDoUserAuthRequestRsaCert(ssh, &pk, WC_HASH_TYPE_SHA, digest, (word32)sizeof(digest)); if (ret != tv->expected) { fprintf(stderr, "\t[%d] \"%s\" FAIL: got %d, expected %d\n", i, tv->name, ret, tv->expected); failures++; } } wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return failures; } #endif /* WOLFSSH_CERTS */ #endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_SSH_RSA_SHA1 */ #ifdef WOLFSSH_SFTP /* Property test for the server-side received-packet size bound applied in * wolfSSH_SFTP_read(). A non-positive size or one above the largest legal * inbound SFTP message must be rejected before any buffer is allocated; * in-range sizes are accepted. */ static int test_SftpRecvSizeBound(void) { int testVals[7]; int expectOk; int ret; int i; int maxWriteBody; testVals[0] = -1; /* underflow / error sentinel */ testVals[1] = 0; /* empty body */ testVals[2] = 1; /* smallest accepted body */ testVals[3] = WOLFSSH_MAX_SFTP_PACKET; /* upper bound, accepted */ testVals[4] = WOLFSSH_MAX_SFTP_PACKET + 1; /* just over the bound */ testVals[5] = 0x40000000; /* the ~1 GB attack value */ testVals[6] = 0x7FFFFFFF; /* INT32_MAX */ for (i = 0; i < (int)(sizeof(testVals) / sizeof(testVals[0])); i++) { ret = wolfSSH_TestSftpRecvSizeCheck(testVals[i]); expectOk = (testVals[i] > 0 && testVals[i] <= WOLFSSH_MAX_SFTP_PACKET); if (expectOk) { if (ret != WS_SUCCESS) return -900 - i; } else { if (ret == WS_SUCCESS) return -920 - i; } } /* The bound value must be large enough to admit a real maximum-size WRITE, * otherwise legitimate large writes would be silently rejected. A WRITE * body is a handle string (length prefix + up to WOLFSSH_MAX_HANDLE), an * 8-byte file offset, then a data string (length prefix + up to * WOLFSSH_MAX_SFTP_RW). Guards against a future shrink of the bound (e.g. * to WOLFSSH_MAX_SFTP_RECV alone). */ maxWriteBody = UINT32_SZ + WOLFSSH_MAX_HANDLE /* handle string */ + (2 * UINT32_SZ) /* 64-bit offset */ + UINT32_SZ + WOLFSSH_MAX_SFTP_RW; /* data string */ if (wolfSSH_TestSftpRecvSizeCheck(maxWriteBody) != WS_SUCCESS) return -930; return 0; } /* IORecv mock that reports no data is available yet. ReceiveData maps this to * WS_WANT_READ, letting the receive loop reach its body-read state without a * live socket. */ static int RecvAlwaysWantRead(WOLFSSH* ssh, void* data, word32 sz, void* ctx) { WOLFSSH_UNUSED(ssh); WOLFSSH_UNUSED(data); WOLFSSH_UNUSED(sz); WOLFSSH_UNUSED(ctx); return WS_CBIO_ERR_WANT_READ; } /* Drives a single crafted SFTP request header through wolfSSH_SFTP_read() on a * fresh server session. The on-wire length field is set to len and the message * type to WRITE; only the 9-byte header is staged in the channel input buffer, * never a body. ioRecv, when non-NULL, stands in for the socket if the loop is * admitted and reaches its body-read state. On success fills *outRet with the * wolfSSH_SFTP_read() return and *outErr with ssh->error, and returns 0; on a * setup failure returns a negative sentinel. DiscardIoSend absorbs the * window-adjust emitted after the header is consumed, and acceptState is * advanced so that adjust is allowed on the bare session. */ static int SftpRecvDriveHeader(word32 len, WS_CallbackIORecv ioRecv, int* outRet, int* outErr) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; int result = 0; byte header[WOLFSSH_SFTP_HEADER]; *outRet = WS_SUCCESS; *outErr = WS_SUCCESS; header[0] = (byte)((len >> 24) & 0xFF); header[1] = (byte)((len >> 16) & 0xFF); header[2] = (byte)((len >> 8) & 0xFF); header[3] = (byte)( len & 0xFF); header[LENGTH_SZ] = WOLFSSH_FTP_WRITE; /* message type */ header[LENGTH_SZ + MSG_ID_SZ + 0] = 0x00; /* request id = 1 */ header[LENGTH_SZ + MSG_ID_SZ + 1] = 0x00; header[LENGTH_SZ + MSG_ID_SZ + 2] = 0x00; header[LENGTH_SZ + MSG_ID_SZ + 3] = 0x01; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return -1000; wolfSSH_SetIOSend(ctx, DiscardIoSend); if (ioRecv != NULL) wolfSSH_SetIORecv(ctx, ioRecv); ssh = wolfSSH_new(ctx); if (ssh == NULL) { result = -1001; goto done; } /* Allow MSGID_CHANNEL_WINDOW_ADJUST on this bare session. */ ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT; ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 1024, 1024); if (ch == NULL) { result = -1002; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -1003; goto done; } if (wolfSSH_TestChannelPutData(ssh->channelList, header, (word32)sizeof(header)) != WS_SUCCESS) { result = -1004; goto done; } *outRet = wolfSSH_SFTP_read(ssh); *outErr = wolfSSH_get_error(ssh); done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* End-to-end check that the receive loop itself rejects an invalid declared * inbound length, not just the SFTP_CheckRecvSz helper in isolation. Drives two * crafted headers whose decoded body length the bound must refuse: one well * past WOLFSSH_MAX_SFTP_PACKET and one of zero. Each must return WS_FATAL_ERROR * with ssh->error == WS_BUFFER_E and allocate no body buffer. The zero case * also guards against ssh->error being left at 0, which a caller could misread * as a clean channel close. */ static int test_SftpRecvSizeBoundIntegration(void) { word32 lens[2]; int rc; int ret; int err; int i; /* On-wire length field counts the type byte, request id, and body. */ lens[0] = (word32)WOLFSSH_MAX_SFTP_PACKET + 100 + MSG_ID_SZ + UINT32_SZ; lens[1] = (word32)(MSG_ID_SZ + UINT32_SZ); /* decoded body length 0 */ for (i = 0; i < (int)(sizeof(lens) / sizeof(lens[0])); i++) { rc = SftpRecvDriveHeader(lens[i], NULL, &ret, &err); if (rc != 0) return rc; if (ret != WS_FATAL_ERROR) return -945 - (i * 10); if (err != WS_BUFFER_E) return -946 - (i * 10); } return 0; } /* End-to-end check that a legitimate maximum-size WRITE is NOT rejected by the * server bound. The decoded body length equals the largest real WRITE body * (handle + 8-byte offset + max data). The bound must admit it: the loop * allocates the body buffer and then asks for the body that has not arrived, so * the call returns WS_FATAL_ERROR with ssh->error == WS_WANT_READ (a benign * retry) rather than WS_BUFFER_E (a bound rejection). RecvAlwaysWantRead stands * in for a non-blocking socket with no data yet. */ static int test_SftpRecvSizeBoundAccept(void) { word32 len; int rc; int ret; int err; /* Largest legitimate WRITE body, matching test_SftpRecvSizeBound, plus the * type byte and request id the on-wire length field carries. */ len = (word32)(UINT32_SZ + WOLFSSH_MAX_HANDLE + (2 * UINT32_SZ) + UINT32_SZ + WOLFSSH_MAX_SFTP_RW) + MSG_ID_SZ + UINT32_SZ; rc = SftpRecvDriveHeader(len, RecvAlwaysWantRead, &ret, &err); if (rc != 0) return rc; if (ret != WS_FATAL_ERROR) return -955; /* WS_WANT_READ: body accepted, loop waiting; WS_BUFFER_E would mean a * legitimate max WRITE was rejected. */ if (err != WS_WANT_READ) return -956; return 0; } #endif /* WOLFSSH_SFTP */ #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_SCP) && \ !defined(WOLFSSH_SCP_USER_CALLBACKS) /* Exercises ExtractFileName, the SCP source helper that splits the leaf name * from a request path. A bare name with no path separator must be accepted * (the whole string is the file name); this is the recursive-source * regression. */ static int test_ScpExtractFileName(void) { char name[64]; int ret; /* bare name, no separator: whole string is the file name (regression) */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("scp_rk_src", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "scp_rk_src") != 0) return -900; /* leading "./" prefix */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("./scp_rk_src", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "scp_rk_src") != 0) return -901; /* relative nested path */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("a/b/c", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "c") != 0) return -902; /* absolute path */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("/tmp/x/y", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "y") != 0) return -903; /* empty path is rejected */ ret = wolfSSH_TestScpExtractFileName("", name, sizeof(name)); if (ret != WS_BAD_ARGUMENT) return -904; /* NULL arguments are rejected */ ret = wolfSSH_TestScpExtractFileName(NULL, name, sizeof(name)); if (ret != WS_BAD_ARGUMENT) return -905; ret = wolfSSH_TestScpExtractFileName("scp_rk_src", NULL, sizeof(name)); if (ret != WS_BAD_ARGUMENT) return -906; /* destination too small for name plus null terminator */ ret = wolfSSH_TestScpExtractFileName("scp_rk_src", name, 4); if (ret != WS_SCP_PATH_LEN_E) return -907; /* bare "." and ".." are accepted as-is (separator-less leaf names); the * recursive walk skips "."/".." directory entries separately */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName(".", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, ".") != 0) return -908; WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("..", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "..") != 0) return -909; /* trailing separator yields an empty leaf name with success */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("a/b/", name, sizeof(name)); if (ret != WS_SUCCESS || WSTRCMP(name, "") != 0) return -910; /* exact-fit boundary of the (fileLen + 1 > fileNameSz) size check: * "scp_rk_src" is 10 chars, so 11 just fits and 10 is one too small */ WMEMSET(name, 0, sizeof(name)); ret = wolfSSH_TestScpExtractFileName("scp_rk_src", name, 11); if (ret != WS_SUCCESS || WSTRCMP(name, "scp_rk_src") != 0) return -911; ret = wolfSSH_TestScpExtractFileName("scp_rk_src", name, 10); if (ret != WS_SCP_PATH_LEN_E) return -912; return 0; } #endif /* WOLFSSH_TEST_INTERNAL && WOLFSSH_SCP && !WOLFSSH_SCP_USER_CALLBACKS */ #endif /* WOLFSSH_TEST_INTERNAL */ /* Error Code And Message Test */ static int test_Errors(void) { const char* errStr; const char* unknownStr = wolfSSH_ErrorToName(1); int result = 0; #ifdef NO_WOLFSSH_STRINGS /* Ensure a valid error code's string matches an invalid code's. * The string is that error strings are not available. */ errStr = wolfSSH_ErrorToName(WS_BAD_ARGUMENT); if (errStr != unknownStr) result = -104; #else int i, j = 0; /* Values that are not or no longer error codes. */ int missing[] = { -1059 }; int missingSz = (int)sizeof(missing)/sizeof(missing[0]); /* Check that all errors have a string and it's the same through the two * APIs. Check that the values that are not errors map to the unknown * string. */ for (i = WS_ERROR; i >= WS_LAST_E; i--) { errStr = wolfSSH_ErrorToName(i); if (j < missingSz && i == missing[j]) { j++; if (errStr != unknownStr) { result = -105; break; } } else { if (errStr == unknownStr) { result = -106; break; } } } /* Check if the next possible value has been given a string. */ if (result == 0) { errStr = wolfSSH_ErrorToName(i); if (errStr != unknownStr) return -107; } #endif return result; } #if defined(WOLFSSH_SFTP) && defined(WOLFSSH_TEST_INTERNAL) /* Inject a crafted SFTP NAME header declaring an on-wire payload length of * 'wireLen' into a channel, drive wolfSSH_SFTP_DoName, and report ssh->error * via outErr. Only the 9-byte header is needed: the NAME size bound is * checked before the message body is read. Returns 0 on setup success. */ static int sftpDoNameInjectErr(word32 wireLen, int* outErr) { WOLFSSH_CTX* ctx = NULL; WOLFSSH* ssh = NULL; WOLFSSH_CHANNEL* ch = NULL; byte hdr[LENGTH_SZ + MSG_ID_SZ + UINT32_SZ]; int result = 0; ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if (ctx == NULL) return -560; ssh = wolfSSH_new(ctx); if (ssh == NULL) { wolfSSH_CTX_free(ctx); return -561; } ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 128, 128); if (ch == NULL) { result = -562; goto done; } if (ChannelAppend(ssh, ch) != WS_SUCCESS) { ChannelDelete(ch, ssh->ctx->heap); result = -563; goto done; } /* SFTP header: [uint32 length][byte type][uint32 reqId]. */ hdr[0] = (byte)(wireLen >> 24); hdr[1] = (byte)(wireLen >> 16); hdr[2] = (byte)(wireLen >> 8); hdr[3] = (byte)(wireLen); hdr[LENGTH_SZ] = WOLFSSH_FTP_NAME; hdr[LENGTH_SZ + MSG_ID_SZ + 0] = 0; hdr[LENGTH_SZ + MSG_ID_SZ + 1] = 0; hdr[LENGTH_SZ + MSG_ID_SZ + 2] = 0; hdr[LENGTH_SZ + MSG_ID_SZ + 3] = 0; /* Leave reqId non-matching so an in-bound header exits at the request-id * check without setting WS_BUFFER_E. */ ssh->reqId = 0xFFFFFFFF; ssh->error = WS_SUCCESS; if (wolfSSH_TestChannelPutData(ch, hdr, (word32)sizeof(hdr)) != WS_SUCCESS) { result = -564; goto done; } *outErr = wolfSSH_TestSftpDoName(ssh); done: wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); return result; } /* Verify wolfSSH_SFTP_DoName rejects a NAME message larger than * WOLFSSH_MAX_SFTP_NAME and accepts one at the limit. SFTP_GetHeader * returns the wire length minus the type and request-id fields, so the * reported maxSz is wireLen - (UINT32_SZ + MSG_ID_SZ). */ static int test_SftpDoName_sizeBound(void) { word32 overhead = UINT32_SZ + MSG_ID_SZ; int err = 0; int result; /* maxSz = WOLFSSH_MAX_SFTP_NAME + 1 -> over the bound, rejected. */ err = WS_SUCCESS; result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead + 1, &err); if (result != 0) return result; if (err != WS_BUFFER_E) return -570; /* maxSz = WOLFSSH_MAX_SFTP_NAME -> at the bound, not rejected (exits at * the request-id check instead). */ err = WS_SUCCESS; result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead, &err); if (result != 0) return result; if (err == WS_BUFFER_E) return -571; /* A wire length above INT_MAX makes SFTP_GetHeader's int result wrap * non-positive; this must be reported as a size error, not a silent * NULL with WS_SUCCESS. */ err = WS_SUCCESS; result = sftpDoNameInjectErr(0x80000000U + overhead, &err); if (result != 0) return result; if (err != WS_BUFFER_E) return -572; return 0; } #endif /* WOLFSSH_SFTP && WOLFSSH_TEST_INTERNAL */ int wolfSSH_UnitTest(int argc, char** argv) { int testResult = 0, unitResult = 0; (void)argc; (void)argv; wolfSSH_Init(); unitResult = test_Errors(); printf("Errors: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_KDF(); printf("KDF: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #ifdef WOLFSSH_TEST_INTERNAL unitResult = test_DoProtoId(); printf("DoProtoId: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_GetMpint(); printf("GetMpint: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if defined(WOLFSSH_TEST_INTERNAL) && \ (!defined(WOLFSSH_NO_HMAC_SHA1) || \ !defined(WOLFSSH_NO_HMAC_SHA1_96) || \ !defined(WOLFSSH_NO_HMAC_SHA2_256) || \ !defined(WOLFSSH_NO_HMAC_SHA2_512)) unitResult = test_DoReceive_VerifyMacFailure(); printf("DoReceiveVerifyMac: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifdef WOLFSSH_TEST_INTERNAL unitResult = test_DoReceive_RejectsShortPadding(); printf("DoReceiveRejectsShortPadding: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256) unitResult = test_DhGexGroupValidate(); printf("DhGexGroupValidate: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifdef WOLFSSH_TEST_INTERNAL unitResult = test_DoUserAuthBanner(); printf("DoUserAuthBanner: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelRequest(); printf("DoChannelRequest: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelSuccess(); printf("DoChannelSuccess: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelFailure(); printf("DoChannelFailure: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelData_overflow(); printf("DoChannelData_overflow: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelExtendedData_overflow(); printf("DoChannelExtendedData_overflow: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoChannelWindowAdjust_overflow(); printf("DoChannelWindowAdjust_overflow: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_SendChannelData_eofTxd(); printf("SendChannelData_eofTxd: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #ifdef WOLFSSH_SFTP unitResult = test_SftpDoName_sizeBound(); printf("SftpDoName_sizeBound: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if !defined(WOLFSSH_NO_RSA) unitResult = test_RsaVerify_BadDigest(); printf("RsaVerify_BadDigest: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_SSH_RSA_SHA1) unitResult = test_DoUserAuthRequestRsa(); printf("DoUserAuthRequestRsa: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_ParseRSAPubKey(); printf("ParseRSAPubKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #ifdef WOLFSSH_CERTS unitResult = test_DoUserAuthRequestRsaCert(); printf("DoUserAuthRequestRsaCert: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #endif #if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) unitResult = test_ParseECCPubKey(); printf("ParseECCPubKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_VERIFY) && \ defined(WOLFSSL_ED25519_STREAMING_VERIFY) unitResult = test_DoUserAuthRequestEd25519(); printf("DoUserAuthRequestEd25519: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_ParseEd25519PubKey(); printf("ParseEd25519PubKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifndef WOLFSSH_NO_MLDSA unitResult = test_DoUserAuthRequestMlDsa(); printf("DoUserAuthRequestMlDsa: %s (result=%d)\n", (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); testResult = testResult || (unitResult != 0); #ifdef WOLFSSH_KEYGEN unitResult = test_PrepareUserAuthRequestMlDsa(); printf("PrepareUserAuthRequestMlDsa: %s (result=%d)\n", (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); testResult = testResult || (unitResult != 0); #ifdef WOLFSSH_CERTS unitResult = test_PrepareUserAuthRequestMlDsaCert(); printf("PrepareUserAuthRequestMlDsaCert: %s (result=%d)\n", (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); testResult = testResult || (unitResult != 0); #endif /* WOLFSSH_CERTS */ #endif /* WOLFSSH_KEYGEN */ unitResult = test_BuildUserAuthRequestMlDsa(); printf("BuildUserAuthRequestMlDsa: %s (result=%d)\n", (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); testResult = testResult || (unitResult != 0); #endif unitResult = test_ChannelPutData(); printf("ChannelPutData: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_SCP) unitResult = test_ScpGetFileMode(); printf("ScpGetFileMode: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif unitResult = test_MsgHighwater(); printf("MsgHighwater: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoUserAuthRequest_serviceName(); printf("DoUserAuthRequest_serviceName: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_DoUserAuthRequest_rejectsPasswordChange(); printf("DoUserAuthRequest_rejectsPasswordChange: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_SendUserAuthFailure_emptyMethods(); printf("SendUserAuthFailure_emptyMethods: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_IdentifyAsn1Key(); printf("IdentifyAsn1Key: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #ifdef WOLFSSH_SFTP unitResult = test_SftpRecvSizeBound(); printf("SftpRecvSizeBound: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_SftpRecvSizeBoundIntegration(); printf("SftpRecvSizeBoundIntegration: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_SftpRecvSizeBoundAccept(); printf("SftpRecvSizeBoundAccept: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \ !defined(NO_FILESYSTEM) && !defined(WOLFSSL_NUCLEUS) && \ !defined(_WIN32) && !defined(WOLFSSH_ZEPHYR) unitResult = test_ScpRecvCallback_EndDirDepthGuard(); printf("ScpRecvCallback_EndDirDepthGuard: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_ScpRecvCallback_NewDirChdirFail(); printf("ScpRecvCallback_NewDirChdirFail: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_ScpRecvCallback_SymlinkGuard(); printf("ScpRecvCallback_SymlinkGuard: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_ScpRecvCallback_Timestamp(); printf("ScpRecvCallback_Timestamp: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #if defined(HAVE_UTIMENSAT) && defined(WOLFSSH_HAVE_SYMLINK) unitResult = test_ScpTimestamp_NoFollow(); printf("ScpTimestamp_NoFollow: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #endif #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_SCP) && \ !defined(WOLFSSH_SCP_USER_CALLBACKS) unitResult = test_ScpExtractFileName(); printf("ScpExtractFileName: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifdef WOLFSSH_TEST_CAPTURING_ALLOCATOR unitResult = test_SshResourceFree_zeroesSecrets(); printf("SshResourceFree_zeroesSecrets: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifndef WOLFSSH_NO_DH unitResult = test_KeyAgreeDh_client_zeroesEphemeralPrivKey(); printf("KeyAgreeDh_client_zeroesEphemeralPrivKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_KeyAgreeDh_server_rejectsBadPeerPublic(); printf("KeyAgreeDh_server_rejectsBadPeerPublic: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_KeyAgreeDh_client_rejectsBadPeerPublic(); printf("KeyAgreeDh_client_rejectsBadPeerPublic: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #if defined(WOLFSSH_SMALL_STACK) && defined(WOLFSSH_TEST_CAPTURING_ALLOCATOR) unitResult = test_KeyAgreeDh_server_zeroesEphemeralPrivKey(); printf("KeyAgreeDh_server_zeroesEphemeralPrivKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif unitResult = test_KeyAgreeDh_server_rejectsOutOfRangePeer(); printf("KeyAgreeDh_server_rejectsOutOfRangePeer: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #ifdef HAVE_FFDHE_2048 unitResult = test_KeyAgreeDh_client_rejectsOutOfRangePeer(); printf("KeyAgreeDh_client_rejectsOutOfRangePeer: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #endif /* !WOLFSSH_NO_DH */ #if !defined(WOLFSSH_NO_ECDH) && !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) unitResult = test_KeyAgreeEcdh_server_rejectsOffCurvePoint(); printf("KeyAgreeEcdh_server_rejectsOffCurvePoint: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_KeyAgreeEcdh_client_rejectsOffCurvePoint(); printf("KeyAgreeEcdh_client_rejectsOffCurvePoint: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif /* !WOLFSSH_NO_ECDH && !WOLFSSH_NO_ECDH_SHA2_NISTP256 */ #endif #ifdef WOLFSSH_TEST_CERTMAN_PROMOTE unitResult = test_CertMan_NoPromoteNonCaIntermediate(); printf("CertMan_NoPromoteNonCaIntermediate: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; unitResult = test_CertMan_PromoteValidCaIntermediate(); printf("CertMan_PromoteValidCaIntermediate: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifdef WOLFSSH_KEYGEN #ifndef WOLFSSH_NO_RSA unitResult = test_RsaKeyGen(); printf("RsaKeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #ifndef WOLFSSH_NO_ECDSA unitResult = test_EcdsaKeyGen(); printf("EcdsaKeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #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; #endif #ifndef WOLFSSH_NO_MLDSA unitResult = test_MlDsaKeyGen(); printf("MlDsaKeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif #endif wolfSSH_Cleanup(); return (testResult ? 1 : 0); } #ifndef NO_UNITTEST_MAIN_DRIVER int main(int argc, char** argv) { return wolfSSH_UnitTest(argc, argv); } #endif