mirror of https://github.com/wolfSSL/wolfssh.git
8285 lines
304 KiB
C
8285 lines
304 KiB
C
/* api.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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_USER_SETTINGS
|
|
#include <wolfssl/wolfcrypt/settings.h>
|
|
#else
|
|
#include <wolfssl/options.h>
|
|
#endif
|
|
#include <wolfssl/wolfcrypt/wc_port.h>
|
|
#include <wolfssh/port.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#if ((defined(WOLFSSH_SFTP) && !defined(NO_WOLFSSH_CLIENT)) || \
|
|
(defined(WOLFSSH_SCP) && defined(WOLFSSH_HAVE_SYMLINK) && \
|
|
!defined(WOLFSSH_SCP_USER_CALLBACKS))) && \
|
|
!defined(SINGLE_THREADED) && !defined(WOLFSSH_ZEPHYR) && \
|
|
!defined(USE_WINDOWS_API)
|
|
/* mkdtemp() and symlink() for the SFTP confinement and SCP symlink-reject
|
|
* tests (staging out-of-jail fixtures and planted links) */
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <wolfssh/ssh.h>
|
|
#include <wolfssh/internal.h>
|
|
#include <wolfssh/log.h>
|
|
#ifdef WOLFSSH_SCP
|
|
#include <wolfssh/wolfscp.h>
|
|
#endif
|
|
#ifdef WOLFSSH_AGENT
|
|
#include <wolfssh/agent.h>
|
|
#endif
|
|
#ifdef WOLFSSH_OSSH_CERTS
|
|
#include <wolfssh/ossh.h>
|
|
#include <wolfssl/wolfcrypt/coding.h>
|
|
#endif
|
|
|
|
#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)
|
|
#define WOLFSSH_TEST_LOCKING
|
|
#ifndef SINGLE_THREADED
|
|
#define WOLFSSH_TEST_THREADING
|
|
#endif
|
|
#define WOLFSSH_TEST_SERVER
|
|
#define WOLFSSH_TEST_ECHOSERVER
|
|
#endif
|
|
#ifndef WOLFSSH_TEST_BLOCK
|
|
#define WOLFSSH_TEST_HEX2BIN
|
|
#endif
|
|
#include <wolfssh/test.h>
|
|
#include "tests/api.h"
|
|
#if defined(WOLFSSH_TEST_ECHOSERVER) || defined(WOLFSSH_TPM)
|
|
/* TPM builds need the echoserver's key loader even without SCP or SFTP,
|
|
* which are what otherwise set WOLFSSH_TEST_ECHOSERVER. */
|
|
#include "examples/echoserver/echoserver.h"
|
|
#endif
|
|
|
|
/* for echoserver test cases */
|
|
int myoptind = 0;
|
|
char* myoptarg = NULL;
|
|
|
|
|
|
#ifndef WOLFSSH_NO_ABORT
|
|
#define WABORT() abort()
|
|
#else
|
|
#define WABORT()
|
|
#endif
|
|
|
|
#define PrintError(description, result) do { \
|
|
printf("\nERROR - %s line %d failed with:", __FILE__, __LINE__); \
|
|
printf("\n expected: "); printf description; \
|
|
printf("\n result: "); printf result; printf("\n\n"); \
|
|
} while(0)
|
|
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
#define Fail(description, result) do { \
|
|
PrintError(description, result); \
|
|
WABORT(); \
|
|
} while(0)
|
|
#else
|
|
#define Fail(description, result) do { \
|
|
PrintError(description, result); \
|
|
WFFLUSH(stdout); \
|
|
WABORT(); \
|
|
} while(0)
|
|
#endif
|
|
|
|
#define Assert(test, description, result) if (!(test)) Fail(description, result)
|
|
|
|
#define AssertTrue(x) Assert( (x), ("%s is true", #x), (#x " => FALSE"))
|
|
#define AssertFalse(x) Assert(!(x), ("%s is false", #x), (#x " => TRUE"))
|
|
|
|
#define AssertNotNull(x) do { \
|
|
PEDANTIC_EXTENSION void* _isNotNull = (void*)(x); \
|
|
Assert(_isNotNull, ("%s is not null", #x), (#x " => NULL")); \
|
|
} while (0)
|
|
|
|
#define AssertNull(x) do { \
|
|
PEDANTIC_EXTENSION void* _isNull = (void*)(x); \
|
|
Assert(!_isNull, ("%s is null", #x), (#x " => %p", _isNull)); \
|
|
} while(0)
|
|
|
|
#define AssertInt(x, y, op, er) do { \
|
|
int _x = (int)(x); \
|
|
int _y = (int)(y); \
|
|
Assert(_x op _y, ("%s " #op " %s", #x, #y), ("%d " #er " %d", _x, _y)); \
|
|
} while(0)
|
|
|
|
#define AssertIntEQ(x, y) AssertInt(x, y, ==, !=)
|
|
#define AssertIntNE(x, y) AssertInt(x, y, !=, ==)
|
|
#define AssertIntGT(x, y) AssertInt(x, y, >, <=)
|
|
#define AssertIntLT(x, y) AssertInt(x, y, <, >=)
|
|
#define AssertIntGE(x, y) AssertInt(x, y, >=, <)
|
|
#define AssertIntLE(x, y) AssertInt(x, y, <=, >)
|
|
|
|
#define AssertStr(x, y, op, er) do { \
|
|
const char* _x = (const char*)(x); \
|
|
const char* _y = (const char*)(y); \
|
|
int _z = (_x && _y) ? strcmp(_x, _y) : -1; \
|
|
Assert(_z op 0, ("%s " #op " %s", #x, #y), \
|
|
("\"%s\" " #er " \"%s\"", _x, _y));\
|
|
} while(0)
|
|
|
|
#define AssertStrEQ(x, y) AssertStr(x, y, ==, !=)
|
|
#define AssertStrNE(x, y) AssertStr(x, y, !=, ==)
|
|
#define AssertStrGT(x, y) AssertStr(x, y, >, <=)
|
|
#define AssertStrLT(x, y) AssertStr(x, y, <, >=)
|
|
#define AssertStrGE(x, y) AssertStr(x, y, >=, <)
|
|
#define AssertStrLE(x, y) AssertStr(x, y, <=, >)
|
|
|
|
#define AssertPtr(x, y, op, er) do { \
|
|
PRAGMA_GCC_DIAG_PUSH \
|
|
/* remarkably, without this inhibition, */ \
|
|
/* the _Pragma()s make the declarations warn. */ \
|
|
PRAGMA_GCC("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") \
|
|
/* inhibit "ISO C forbids conversion of function pointer */ \
|
|
/* to object pointer type [-Werror=pedantic]" */ \
|
|
PRAGMA_GCC("GCC diagnostic ignored \"-Wpedantic\"") \
|
|
void* _x = (void*)(x); \
|
|
void* _y = (void*)(y); \
|
|
Assert(_x op _y, ("%s " #op " %s", #x, #y), ("%p " #er " %p", _x, _y)); \
|
|
PRAGMA_GCC_DIAG_POP; \
|
|
} while(0)
|
|
|
|
#define AssertPtrEq(x, y) AssertPtr(x, y, ==, !=)
|
|
#define AssertPtrNE(x, y) AssertPtr(x, y, !=, ==)
|
|
#define AssertPtrGT(x, y) AssertPtr(x, y, >, <=)
|
|
#define AssertPtrLT(x, y) AssertPtr(x, y, <, >=)
|
|
#define AssertPtrGE(x, y) AssertPtr(x, y, >=, <)
|
|
#define AssertPtrLE(x, y) AssertPtr(x, y, <=, >)
|
|
|
|
|
|
#ifndef WOLFSSH_TEST_BLOCK
|
|
|
|
enum WS_TestEndpointTypes {
|
|
TEST_GOOD_ENDPOINT_SERVER = WOLFSSH_ENDPOINT_SERVER,
|
|
TEST_GOOD_ENDPOINT_CLIENT = WOLFSSH_ENDPOINT_CLIENT,
|
|
TEST_BAD_ENDPOINT_NEXT,
|
|
TEST_BAD_ENDPOINT_LAST = 255
|
|
};
|
|
|
|
static void test_wolfSSH_CTX_new(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
|
|
AssertNull(ctx = wolfSSH_CTX_new(TEST_BAD_ENDPOINT_NEXT, NULL));
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
AssertNull(ctx = wolfSSH_CTX_new(TEST_BAD_ENDPOINT_LAST, NULL));
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(TEST_GOOD_ENDPOINT_SERVER, NULL));
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(TEST_GOOD_ENDPOINT_CLIENT, NULL));
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
static void test_server_wolfSSH_new(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
|
|
AssertNull(ssh = wolfSSH_new(NULL));
|
|
wolfSSH_free(ssh);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
static void test_client_wolfSSH_new(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
|
|
AssertNull(ssh = wolfSSH_new(NULL));
|
|
wolfSSH_free(ssh);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_set_fd(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
WS_SOCKET_T fd = 23, check;
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_set_fd(NULL, fd));
|
|
check = wolfSSH_get_fd(NULL);
|
|
#ifdef USE_WINDOWS_API
|
|
AssertTrue(INVALID_SOCKET == check);
|
|
#else
|
|
AssertTrue(-1 == check);
|
|
#endif
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_set_fd(ssh, fd));
|
|
check = wolfSSH_get_fd(ssh);
|
|
AssertTrue(fd == check);
|
|
AssertTrue(0 != check);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_SetUsername(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_CLIENT
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
const char username[] = "johnny";
|
|
const char empty[] = "";
|
|
const char* name = NULL;
|
|
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsername(NULL, NULL));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsernameRaw(NULL, NULL, 0));
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetUsername(ssh, username));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsername(ssh, NULL));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsername(ssh, empty));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsernameRaw(ssh, NULL, 0));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsernameRaw(ssh, NULL, 23));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsernameRaw(ssh,
|
|
(const byte*)empty, 0));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsernameRaw(ssh,
|
|
(const byte*)username, 0));
|
|
wolfSSH_free(ssh);
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetUsername(ssh, username));
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetUsernameRaw(ssh,
|
|
(const byte*)username, (word32)strlen(username)));
|
|
AssertNotNull((name = wolfSSH_GetUsername(ssh)));
|
|
AssertIntEQ(0, strcmp(username, name));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif /* WOLFSSH_NO_CLIENT */
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_SetChannelType(void)
|
|
{
|
|
#ifndef NO_WOLFSSH_CLIENT
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
const byte sub1[] = "sftp";
|
|
const byte sub2[] = "a-longer-subsystem-name";
|
|
byte* prevName;
|
|
byte* maxName;
|
|
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetChannelType(NULL,
|
|
WOLFSSH_SESSION_SHELL, NULL, 0));
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SHELL, NULL, 0));
|
|
AssertNull(ssh->channelName);
|
|
AssertIntEQ(0, ssh->channelNameSz);
|
|
|
|
/* subsystem carries a required name string, so with none stored and
|
|
* none given the request would go out without one */
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, NULL, 0));
|
|
AssertNull(ssh->channelName);
|
|
/* a refused call leaves the selected type alone, not just the name */
|
|
AssertIntEQ(WOLFSSH_SESSION_SHELL, ssh->connectChannelId);
|
|
|
|
/* likewise for a size with no name behind it */
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, NULL, 4));
|
|
AssertNull(ssh->channelName);
|
|
AssertIntEQ(WOLFSSH_SESSION_SHELL, ssh->connectChannelId);
|
|
|
|
/* an oversized name is reported, not silently dropped */
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub1, WOLFSSH_MAX_CHN_NAMESZ));
|
|
AssertNull(ssh->channelName);
|
|
AssertIntEQ(WOLFSSH_SESSION_SHELL, ssh->connectChannelId);
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub1,
|
|
(word32)(sizeof(sub1) - 1)));
|
|
AssertNotNull(ssh->channelName);
|
|
AssertIntEQ((int)(sizeof(sub1) - 1), (int)ssh->channelNameSz);
|
|
AssertIntEQ(0, strcmp((const char*)ssh->channelName, (const char*)sub1));
|
|
AssertIntEQ(0, ssh->channelName[ssh->channelNameSz]); /* NUL terminated */
|
|
|
|
/* re-setting the same name must not reallocate (no churn) */
|
|
prevName = ssh->channelName;
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub1,
|
|
(word32)(sizeof(sub1) - 1)));
|
|
AssertIntEQ(1, ssh->channelName == prevName);
|
|
|
|
/* a rejected (oversize) name must leave the previous buffer intact */
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub1, WOLFSSH_MAX_CHN_NAMESZ));
|
|
AssertIntEQ(1, ssh->channelName == prevName);
|
|
AssertIntEQ(WOLFSSH_SESSION_SUBSYSTEM, ssh->connectChannelId);
|
|
AssertIntEQ((int)(sizeof(sub1) - 1), (int)ssh->channelNameSz);
|
|
AssertIntEQ(0, strcmp((const char*)ssh->channelName, (const char*)sub1));
|
|
|
|
/* a zero-length name is ignored and preserves the stored name */
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub1, 0));
|
|
AssertIntEQ(1, ssh->channelName == prevName);
|
|
AssertIntEQ((int)(sizeof(sub1) - 1), (int)ssh->channelNameSz);
|
|
|
|
/* the largest name the limit still admits is stored, pinning the
|
|
* other side of the boundary the oversize checks above cover */
|
|
AssertNotNull(maxName = (byte*)malloc(WOLFSSH_MAX_CHN_NAMESZ - 1));
|
|
memset(maxName, 'a', WOLFSSH_MAX_CHN_NAMESZ - 1);
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, maxName,
|
|
WOLFSSH_MAX_CHN_NAMESZ - 1));
|
|
AssertIntEQ(WOLFSSH_MAX_CHN_NAMESZ - 1, (int)ssh->channelNameSz);
|
|
AssertIntEQ(0, memcmp(ssh->channelName, maxName,
|
|
WOLFSSH_MAX_CHN_NAMESZ - 1));
|
|
AssertIntEQ(0, ssh->channelName[ssh->channelNameSz]); /* NUL terminated */
|
|
free(maxName);
|
|
|
|
/* repeated set frees the previous buffer before replacing it */
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SUBSYSTEM, (byte*)sub2,
|
|
(word32)(sizeof(sub2) - 1)));
|
|
AssertNotNull(ssh->channelName);
|
|
AssertIntEQ((int)(sizeof(sub2) - 1), (int)ssh->channelNameSz);
|
|
AssertIntEQ(0, strcmp((const char*)ssh->channelName, (const char*)sub2));
|
|
|
|
/* EXEC reaches the same alloc path via fallthrough on the client side */
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_EXEC, (byte*)sub1,
|
|
(word32)(sizeof(sub1) - 1)));
|
|
AssertNotNull(ssh->channelName);
|
|
AssertIntEQ((int)(sizeof(sub1) - 1), (int)ssh->channelNameSz);
|
|
AssertIntEQ(0, strcmp((const char*)ssh->channelName, (const char*)sub1));
|
|
|
|
/* switching to SHELL frees and clears a prior subsystem/exec name */
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_SHELL, NULL, 0));
|
|
AssertNull(ssh->channelName);
|
|
AssertIntEQ(0, ssh->channelNameSz);
|
|
|
|
/* unknown channel type is rejected */
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_UNKNOWN, NULL, 0));
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
/* server-side EXEC is rejected */
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetChannelType(ssh,
|
|
WOLFSSH_SESSION_EXEC, (byte*)sub1, (word32)(sizeof(sub1) - 1)));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif /* NO_WOLFSSH_CLIENT */
|
|
}
|
|
|
|
|
|
enum WS_TestFormatTypes {
|
|
TEST_GOOD_FORMAT_ASN1 = WOLFSSH_FORMAT_ASN1,
|
|
TEST_GOOD_FORMAT_PEM = WOLFSSH_FORMAT_PEM,
|
|
TEST_GOOD_FORMAT_RAW = WOLFSSH_FORMAT_RAW,
|
|
TEST_BAD_FORMAT_NEXT,
|
|
TEST_BAD_FORMAT_LAST = 0xFFFF
|
|
};
|
|
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
static const char serverKeyEccDer[] =
|
|
"307702010104206109990b79d25f285a0f5d15cca15654f92b3987212da77d85"
|
|
"7bb87f38c66dd5a00a06082a8648ce3d030107a144034200048113ffa42bb79c"
|
|
"45747a834c61f33fad26cf22cda9a3bca561b47ce662d4c2f755439a31fb8011"
|
|
"20b5124b24f578d7fd22ef4635f005586b5f63c8da1bc4f569";
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
static const byte serverKeyEccCurveId = ID_ECDSA_SHA2_NISTP256;
|
|
#endif
|
|
#elif !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384)
|
|
static const char serverKeyEccDer[] =
|
|
"3081a402010104303eadd2bbbf05a7be3a3f7c28151289de5bb3644d7011761d"
|
|
"b56f2a0362fba64f98e64ff986dc4fb8efdb2d6b8da57142a00706052b810400"
|
|
"22a1640362000438d62be418ff573fd0e020d48876c4e1121dfb2d6ebee4895d"
|
|
"7724316d46a23105873f2986d5c712803a6f471ab86850eb063e108961349cf8"
|
|
"b4c6a4cf5e97bd7e51e975e3e9217261506eb9cf3c493d3eb88d467b5f27ebab"
|
|
"2161c00066febd";
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
static const byte serverKeyEccCurveId = ID_ECDSA_SHA2_NISTP384;
|
|
#endif
|
|
#elif !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521)
|
|
static const char serverKeyEccDer[] =
|
|
"3081dc0201010442004ca4d86428d9400e7b2df3912eb996c195895043af92e8"
|
|
"6de70ae4df46f22a291a6bb2748aae82580df6c39f49b3ed82f1789ece1b657d"
|
|
"45438cff156534354575a00706052b81040023a18189038186000401f8d0a7c3"
|
|
"c58d841957969f213a94f3da550edf76d8dd171531f35bb069c8bc300d6f6b37"
|
|
"d18046a9717f2c6f59519c827095b29a6313306218c235769400d0f96d000a19"
|
|
"3ba346652beb409a9a45c597a3ed932dd5aaae96bf2f317e5a7ac7458b3c6cdb"
|
|
"aa90c355382cdfcdca7377d92eb20a5e8c74237ca5a345b19e3f1a2290b154";
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
static const byte serverKeyEccCurveId = ID_ECDSA_SHA2_NISTP521;
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef WOLFSSH_NO_RSA
|
|
static const char serverKeyRsaDer[] =
|
|
"308204a30201000282010100da5dad2514761559f340fd3cb86230b36dc0f9ec"
|
|
"ec8b831e9e429cca416ad38ae15234e00d13627ed40fae5c4d04f18dfac5ad77"
|
|
"aa5a05caeff88dabff8a29094c04c2f519cbed1fb1b429d3c36ca923dfa3a0e5"
|
|
"08dead8c71f934886ced3bf06fa50fac59ff6b33f170fb8ca4b345228d9d777a"
|
|
"e5295f8414d999eaeace2d51f3e358fa5b020fc9b52abcb25ed3c230bb3cb1c3"
|
|
"ef58f35094288bc4654af700d997d96b4d8d95a18a6206b450112283b4ea2ae7"
|
|
"d0a820474fff46aec513e1388bf854af3a4d2ff81fd78490d8930506c27d90db"
|
|
"e39cd0c4655a03ad00ac5aa2cdda3f89583753bf2b467aac89412b5a2ee876e7"
|
|
"5ee32985a363eae686607c2d02030100010281ff0f911e06c6aea45705405ccd"
|
|
"3757c8a101f1ffdf23fdce1b20ad1f004c29916b1525071ff1ceaff6daa74386"
|
|
"d0f6c94195df01bec62624c392d7e5419db5fbb6edf468f19025398248e8cf12"
|
|
"899bf572d93e90f9c2e81cf72628ddd5dbee0d97d65dae005b6a19fa59fbf3f2"
|
|
"d2caf4e2c1b5b80ecac76847c234c1043e38f4820159f28a6ef76b5b0abc05a9"
|
|
"2737b9f9068054e8701ab432936bf526c786f4580543f9728fec42a03bba3562"
|
|
"ccecf4b304a2ebae3c87408efe8fdd14bebd83c9c918ca817c06f9e3992eec29"
|
|
"c52756ea1e93c6e80c44ca73684a7fae16251d1225142aec416925c35de6aee4"
|
|
"59801dfabd9f3336939d88d688c95b277b0b6102818100de01abfa65d2fad26f"
|
|
"fe3f576d757f8ce6bdfe08bdc71334620e87b27a2ca9cdca93d83191812dd668"
|
|
"96aa25e3b87ea598a8e8153cc0cedef5ab80b1f5baafac9cc1b34334ae22f718"
|
|
"418663a2448e1b419d2d756f0d5b10195d14aa801fee023ef8b6f6ec658e3889"
|
|
"0d0b50e41149863982db73e53a0f1322abada0789b942102818100fbcd4c5249"
|
|
"3f2c8094914a38ec0f4a7d3a8ebc0490152584fbd368bdefa047fece5bbf1d2a"
|
|
"9427fc5170ffc9e9babe2ba05025d3e1a15733cc5cc77d09f6dcfb72943dca59"
|
|
"5273e06c450ad9da30df2b33d752184101f0df1b01c1d3b79b26f81c8fffc819"
|
|
"fd36d013a57242a3305957b4da2a09e5455a396d70220cba53268d02818100b1"
|
|
"3cc270f093c43cf6be1311984882e11961bb0a7d800e3bf6c0c4e2df19032351"
|
|
"44410829b2e8c650cf5fdd49f503deee86826a5a0b4fdcbe63022691184ea1ce"
|
|
"aff18e88e330f4f5ff71ebdf233e145288ca3f03beb4e1a06e284e8a65735d85"
|
|
"aa885f8f90f03f006352926cd1c4520d5e04177d7ca186545a9d0e0cdba02102"
|
|
"818100eafe1b9e27b1876cb03a2f9493e9695119971facfa7261c38be92eb523"
|
|
"aee7c1cb002089adb4fae4257559a22c3915454da5bec7d0a86be371739cd0fa"
|
|
"bda25a20026cf02d1020086fc2b76fbc8b239b04148d0f098c302966e0eaed15"
|
|
"4afcc14c96aed5263c042d88483d2c2773f5cd3e80e3febc334f128d29bafd39"
|
|
"de63f9028181008b1f47a2904b823b892de96be128e5228783d0de1e0d8ccc84"
|
|
"433d238d9d6cbcc4c6da44447920b63eefcf8ac438b0e5da45ac5acc7b62baa9"
|
|
"731fba275c82f8ad311edef33772cb47d2cdf7f87f0039db8d2aca4ec1cee215"
|
|
"89d63a61ae9da230a585ae38ea4674dc023aace95fa3c6734f73819056c3ce77"
|
|
"5f5bba6c42f121";
|
|
#endif
|
|
|
|
|
|
static void test_wolfSSH_CTX_UsePrivateKey_buffer(void)
|
|
{
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
WOLFSSH_CTX* ctx;
|
|
#ifndef WOLFSSH_NO_ECDSA
|
|
byte* eccKey;
|
|
word32 eccKeySz;
|
|
#endif
|
|
#ifndef WOLFSSH_NO_RSA
|
|
byte* rsaKey;
|
|
word32 rsaKeySz;
|
|
#endif
|
|
const byte* lastKey = NULL;
|
|
word32 lastKeySz = 0;
|
|
int i;
|
|
|
|
#ifndef WOLFSSH_NO_ECC
|
|
AssertIntEQ(0,
|
|
ConvertHexToBin(serverKeyEccDer, &eccKey, &eccKeySz,
|
|
NULL, NULL, NULL,
|
|
NULL, NULL, NULL,
|
|
NULL, NULL, NULL));
|
|
#endif
|
|
#ifndef WOLFSSH_NO_RSA
|
|
AssertIntEQ(0,
|
|
ConvertHexToBin(serverKeyRsaDer, &rsaKey, &rsaKeySz,
|
|
NULL, NULL, NULL,
|
|
NULL, NULL, NULL,
|
|
NULL, NULL, NULL));
|
|
#endif
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
for (i = 0; i < WOLFSSH_MAX_PVT_KEYS; i++) {
|
|
AssertNull(ctx->privateKey[i].key);
|
|
AssertIntEQ(0, ctx->privateKey[i].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[i].publicKeyFmt);
|
|
}
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Fail: all NULL/BAD */
|
|
AssertIntNE(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(NULL, NULL, 0, TEST_BAD_FORMAT_NEXT));
|
|
AssertNull(ctx->privateKey[0].key);
|
|
AssertIntEQ(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[0].publicKeyFmt);
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Fail: ctx set, others NULL/bad */
|
|
AssertIntNE(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, NULL, 0, TEST_BAD_FORMAT_NEXT));
|
|
AssertNull(ctx->privateKey[0].key);
|
|
AssertIntEQ(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[0].publicKeyFmt);
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Fail: ctx set, key set, others bad */
|
|
AssertIntNE(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx,
|
|
lastKey, 0, TEST_BAD_FORMAT_NEXT));
|
|
AssertNull(ctx->privateKey[0].key);
|
|
AssertIntEQ(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[0].publicKeyFmt);
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Fail: ctx set, keySz set, others NULL/bad */
|
|
AssertIntNE(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, NULL, 1, TEST_BAD_FORMAT_NEXT));
|
|
AssertNull(ctx->privateKey[0].key);
|
|
AssertIntEQ(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[0].publicKeyFmt);
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Fail: ctx set, key set, keySz set, format invalid */
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_CTX_UsePrivateKey_buffer(ctx,
|
|
lastKey, lastKeySz, TEST_GOOD_FORMAT_PEM));
|
|
AssertNull(ctx->privateKey[0].key);
|
|
AssertIntEQ(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(ID_NONE, ctx->privateKey[0].publicKeyFmt);
|
|
AssertIntEQ(0, ctx->privateKeyCount);
|
|
|
|
/* Pass */
|
|
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521)
|
|
lastKey = ctx->privateKey[ctx->privateKeyCount].key;
|
|
lastKeySz = ctx->privateKey[ctx->privateKeyCount].keySz;
|
|
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, eccKey, eccKeySz,
|
|
TEST_GOOD_FORMAT_ASN1));
|
|
AssertIntEQ(1, ctx->privateKeyCount);
|
|
AssertNotNull(ctx->privateKey[0].key);
|
|
AssertIntNE(0, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(serverKeyEccCurveId, ctx->privateKey[0].publicKeyFmt);
|
|
|
|
AssertIntEQ(0, (lastKey == ctx->privateKey[0].key));
|
|
AssertIntNE(lastKeySz, ctx->privateKey[0].keySz);
|
|
#endif
|
|
|
|
#ifndef WOLFSSH_NO_SSH_RSA_SHA1
|
|
lastKey = ctx->privateKey[ctx->privateKeyCount].key;
|
|
lastKeySz = ctx->privateKey[ctx->privateKeyCount].keySz;
|
|
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, rsaKey, rsaKeySz,
|
|
TEST_GOOD_FORMAT_ASN1));
|
|
AssertIntNE(0, ctx->privateKeyCount);
|
|
AssertNotNull(ctx->privateKey[0].key);
|
|
AssertIntNE(0, ctx->privateKey[0].keySz);
|
|
|
|
AssertIntEQ(0, (lastKey == ctx->privateKey[0].key));
|
|
AssertIntNE(lastKeySz, ctx->privateKey[0].keySz);
|
|
#endif
|
|
|
|
/* Add the same keys again. This should succeed. */
|
|
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521)
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, eccKey, eccKeySz,
|
|
TEST_GOOD_FORMAT_ASN1));
|
|
#endif
|
|
#ifndef WOLFSSH_NO_SSH_RSA_SHA1
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, rsaKey, rsaKeySz,
|
|
TEST_GOOD_FORMAT_ASN1));
|
|
#endif
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521)
|
|
FreeBins(eccKey, NULL, NULL, NULL);
|
|
#endif
|
|
#ifndef WOLFSSH_NO_RSA
|
|
FreeBins(rsaKey, NULL, NULL, NULL);
|
|
#endif
|
|
#endif /* NO_WOLFSSH_SERVER */
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_CERTS
|
|
static int load_file(const char* filename, byte** buf, word32* bufSz)
|
|
{
|
|
FILE* f = NULL;
|
|
int ret = 0;
|
|
|
|
if (filename == NULL || buf == NULL || bufSz == NULL)
|
|
ret = -1;
|
|
|
|
if (ret == 0) {
|
|
f = fopen(filename, "rb");
|
|
if (f == NULL)
|
|
ret = -2;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = fseek(f, 0, XSEEK_END);
|
|
if (ret < 0)
|
|
ret = -3;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
long sz = ftell(f);
|
|
if (sz < 0)
|
|
ret = -4;
|
|
else
|
|
*bufSz = (word32)sz;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = fseek(f, 0, XSEEK_SET);
|
|
if (ret < 0)
|
|
ret = -8;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
*buf = (byte*)malloc(*bufSz);
|
|
if (*buf == NULL)
|
|
ret = -5;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
size_t readSz;
|
|
readSz = fread(*buf, 1, *bufSz, f);
|
|
if (readSz < *bufSz)
|
|
ret = -6;
|
|
}
|
|
|
|
if (f != NULL) {
|
|
ret = fclose(f);
|
|
if (ret < 0)
|
|
ret = -7;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef WOLFSSH_CERTS
|
|
|
|
/* PEM shapes that carry a header the sniff accepts but a body no decoder
|
|
* will take, so the failure lands in the decoder rather than the sniff. */
|
|
static const char badPemCert[] =
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
"!!!! this is not base64 !!!!\n"
|
|
"-----END CERTIFICATE-----\n";
|
|
static const char noBodyPemCert[] = "-----BEGIN CERTIFICATE-----\n";
|
|
/* Under one full base64 group, so the body decodes to nothing rather than
|
|
* failing, and wolfSSL answers 0 for it instead of a negative code. */
|
|
static const char zeroLenPemCert[] =
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
"MI\n"
|
|
"-----END CERTIFICATE-----\n";
|
|
|
|
#endif /* WOLFSSH_CERTS */
|
|
|
|
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
|
|
/* The same shapes for a private key, which decodes without certificate
|
|
* support and so is pinned outside WOLFSSH_CERTS. */
|
|
static const char badPemKey[] =
|
|
"-----BEGIN PRIVATE KEY-----\n"
|
|
"!!!! this is not base64 !!!!\n"
|
|
"-----END PRIVATE KEY-----\n";
|
|
/* This one goes through wc_KeyPemToDer, which answers 0 rather than a
|
|
* negative code, so it needs its own fixture. */
|
|
static const char zeroLenPemKey[] =
|
|
"-----BEGIN PRIVATE KEY-----\n"
|
|
"MI\n"
|
|
"-----END PRIVATE KEY-----\n";
|
|
|
|
#endif /* NO_WOLFSSH_SERVER */
|
|
|
|
|
|
static void test_wolfSSH_CTX_UseCert_buffer(void)
|
|
{
|
|
#ifdef WOLFSSH_CERTS
|
|
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
byte* cert = NULL;
|
|
word32 certSz = 0;
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
byte* key = NULL;
|
|
word32 keySz = 0;
|
|
word32 count = 0;
|
|
byte lastFmt = ID_NONE;
|
|
#endif
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
AssertIntEQ(0, load_file("./keys/server-cert.pem", &cert, &certSz));
|
|
AssertNotNull(cert);
|
|
AssertIntNE(0, certSz);
|
|
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_UseCert_buffer(NULL, cert, certSz, WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, NULL, certSz, WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, NULL, 0, WOLFSSH_FORMAT_PEM));
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(1, ctx->privateKeyCount);
|
|
AssertNotNull(ctx->privateKey[0].cert);
|
|
/* A certificate with no key behind it has no signing source, so
|
|
* RefreshPublicKeyAlgo must not advertise it yet; loading the matching
|
|
* key below is what makes the slot advertisable. */
|
|
AssertIntEQ(0, ctx->publicKeyAlgoCount);
|
|
#endif
|
|
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_RAW));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, 99));
|
|
|
|
/* Content the caller declared PEM but that will not decode is malformed
|
|
* input, not a file that would not read. */
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, (const byte*)badPemCert,
|
|
(word32)WSTRLEN(badPemCert), WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, (const byte*)noBodyPemCert,
|
|
(word32)WSTRLEN(noBodyPemCert), WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, (const byte*)zeroLenPemCert,
|
|
(word32)WSTRLEN(zeroLenPemCert), WOLFSSH_FORMAT_PEM));
|
|
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
/* A matching private key seeds a key copy in the cert slot. */
|
|
AssertIntEQ(0, load_file("./keys/server-key-ecc.der", &key, &keySz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, key, keySz,
|
|
WOLFSSH_FORMAT_ASN1));
|
|
count = ctx->privateKeyCount;
|
|
AssertIntEQ(2, count);
|
|
#endif
|
|
|
|
/* A different certificate, so the reload shows in the stored DER. */
|
|
AssertIntEQ(0, load_file("./keys/fred-cert.der", &cert, &certSz));
|
|
AssertNotNull(cert);
|
|
AssertIntNE(0, certSz);
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
|
/* Reloading replaces the slot instead of appending a duplicate. */
|
|
AssertIntEQ(count, ctx->privateKeyCount);
|
|
AssertIntEQ(certSz, ctx->privateKey[0].certSz);
|
|
AssertIntEQ(0, XMEMCMP(ctx->privateKey[0].cert, cert, certSz));
|
|
AssertIntEQ(2, ctx->publicKeyAlgoCount);
|
|
/* The replaced slot keeps a fresh copy of the matching key. */
|
|
AssertIntEQ(ctx->privateKey[1].keySz, ctx->privateKey[0].keySz);
|
|
AssertIntEQ(0, XMEMCMP(ctx->privateKey[0].key, ctx->privateKey[1].key,
|
|
ctx->privateKey[0].keySz));
|
|
|
|
/* A full table still replaces the matching slot rather than rejecting;
|
|
* a third certificate keeps the stored-DER checks honest. */
|
|
free(cert);
|
|
cert = NULL;
|
|
AssertIntEQ(0, load_file("./keys/server-cert.der", &cert, &certSz));
|
|
ctx->privateKeyCount = WOLFSSH_MAX_PVT_KEYS;
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
|
AssertIntEQ(certSz, ctx->privateKey[0].certSz);
|
|
AssertIntEQ(0, XMEMCMP(ctx->privateKey[0].cert, cert, certSz));
|
|
/* publicKeyAlgo stays stale from the fabricated count; ctx freed below. */
|
|
ctx->privateKeyCount = count;
|
|
|
|
/* No matching slot and no room: rejected, and the DER is freed. */
|
|
lastFmt = ctx->privateKey[0].publicKeyFmt;
|
|
ctx->privateKey[0].publicKeyFmt = ID_NONE;
|
|
ctx->privateKeyCount = WOLFSSH_MAX_PVT_KEYS;
|
|
AssertIntEQ(WS_CTX_KEY_COUNT_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
|
AssertIntEQ(WOLFSSH_MAX_PVT_KEYS, ctx->privateKeyCount);
|
|
ctx->privateKeyCount = count;
|
|
ctx->privateKey[0].publicKeyFmt = lastFmt;
|
|
#endif
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
free(cert);
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
free(key);
|
|
#endif
|
|
#endif /* WOLFSSH_CERTS */
|
|
}
|
|
|
|
|
|
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
|
|
|
|
/* Public key lines. An x509v3-* line carries an RFC 6187 wire chain rather
|
|
* than a certificate, so this API declines it the same as a plain key. */
|
|
static const char x509v3EccLine[] =
|
|
"x509v3-ecdsa-sha2-nistp256 AAAAB3NzaC1yc2EAAAA=\n";
|
|
static const char sshRsaLine[] = "ssh-rsa AAAAB3NzaC1yc2EAAAA=\n";
|
|
static const byte notACert[] = { 0x01, 0x02, 0x03, 0x04, 0x05 };
|
|
|
|
#if defined(WOLFSSH_OSSH_CERTS) && !defined(WOLFSSH_NO_ED25519) && \
|
|
!defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
|
|
#define WOLFSSH_TEST_OSSH_CERT_FILE
|
|
|
|
/* The name routes the line. The blob behind it is not a certificate, so a
|
|
* reader that decodes the blob rejects it there instead. */
|
|
static const char osshCertLine[] =
|
|
"ssh-ed25519-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAA=\n";
|
|
static const char osshCertPath[] = "./ossh-cert-line.tmp";
|
|
|
|
/* Stages content in a file, the only form the file APIs take. Returns 0 on
|
|
* success. */
|
|
static int writeTmpFile(const char* path, const void* data, size_t sz)
|
|
{
|
|
FILE* f = NULL;
|
|
int ret = 0;
|
|
|
|
f = fopen(path, "wb");
|
|
if (f == NULL)
|
|
ret = -1;
|
|
|
|
if (ret == 0) {
|
|
if (fwrite(data, 1, sz, f) != sz)
|
|
ret = -2;
|
|
/* Close either way, but keep the write error as the reason. */
|
|
if (fclose(f) != 0 && ret == 0)
|
|
ret = -3;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* OSSH_CERTS && ED25519 && FILESYSTEM */
|
|
|
|
|
|
static void test_wolfSSH_ReadCert_buffer(void)
|
|
{
|
|
byte* out = NULL;
|
|
word32 outSz = 0;
|
|
const byte* outType = NULL;
|
|
word32 outTypeSz = 0;
|
|
byte flavor = 0xFF;
|
|
#ifdef WOLFSSH_CERTS
|
|
byte* cert = NULL;
|
|
word32 certSz = 0;
|
|
byte stale[1];
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
int ret;
|
|
#endif
|
|
#endif
|
|
|
|
/* Every out parameter is required, and so is a non-empty input. */
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(NULL,
|
|
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
|
|
&flavor, NULL));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert, 0,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert,
|
|
sizeof(notACert), NULL, &outSz, &outType, &outTypeSz,
|
|
&flavor, NULL));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert,
|
|
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
|
|
NULL, NULL));
|
|
|
|
/* Content that is not a certificate, a public key line included. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(notACert,
|
|
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
|
|
&flavor, NULL));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(
|
|
(const byte*)sshRsaLine, (word32)WSTRLEN(sshRsaLine),
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(
|
|
(const byte*)x509v3EccLine, (word32)WSTRLEN(x509v3EccLine),
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
/* A rejection must not leave the caller's flavor standing as an answer. */
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
#ifdef WOLFSSH_CERTS
|
|
AssertIntEQ(0, load_file("./keys/server-cert.pem", &cert, &certSz));
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntGT(outSz, 0);
|
|
AssertIntEQ(out[0], 0x30);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
|
|
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
|
|
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
out = NULL;
|
|
#else
|
|
/* Every fixture is ECDSA P-256. With its x509v3 name compiled out the key
|
|
* identifies but has no name to be reported under. */
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
#endif
|
|
|
|
/* Half a PEM loses the CERTIFICATE header, so the sniff declines it. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(cert, certSz / 2,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
/* Keeping the header sends these past the sniff and into the decoder,
|
|
* where a body that will not decode is a parse failure, not a file error.
|
|
* Sentinels go in, as the preceding rejection already cleared them all. */
|
|
out = stale;
|
|
outSz = 0xDEADBEEF;
|
|
outType = stale;
|
|
outTypeSz = 0xDEADBEEF;
|
|
flavor = WOLFSSH_CERT_FLAVOR_X509;
|
|
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer((const byte*)badPemCert,
|
|
(word32)WSTRLEN(badPemCert),
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
out = stale;
|
|
outSz = 0xDEADBEEF;
|
|
outType = stale;
|
|
outTypeSz = 0xDEADBEEF;
|
|
flavor = WOLFSSH_CERT_FLAVOR_X509;
|
|
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer((const byte*)noBodyPemCert,
|
|
(word32)WSTRLEN(noBodyPemCert),
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
AssertIntEQ(0, load_file("./keys/server-cert.der", &cert, &certSz));
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntEQ(outSz, certSz);
|
|
AssertIntEQ(0, WMEMCMP(out, cert, certSz));
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
|
|
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
out = NULL;
|
|
#else
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
#endif
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
/* Not a certificate behind the DER header. The wolfSSL error is mapped,
|
|
* so a WS_ code reaches the caller. The read above left a name behind, so
|
|
* this also shows a failure clearing one. */
|
|
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer(
|
|
(const byte*)"\x30\x82\x01\x02",
|
|
4, &out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
|
|
/* A private key is the likeliest mistake, and the DER one also leads with
|
|
* 0x30, so only the parse tells them apart. */
|
|
AssertIntEQ(0, load_file("./keys/server-key-ecc.der", &cert, &certSz));
|
|
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
/* The PEM one has no CERTIFICATE header, so the sniff declines it. Every
|
|
* out param goes in set to prove a rejection clears them all. */
|
|
AssertIntEQ(0, load_file("./keys/server-key-ecc.pem", &cert, &certSz));
|
|
out = cert;
|
|
outSz = 0xDEADBEEF;
|
|
outType = cert;
|
|
outTypeSz = 0xDEADBEEF;
|
|
flavor = WOLFSSH_CERT_FLAVOR_X509;
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
/* Ed25519 must never load: it has no x509v3 name, and today the key
|
|
* inside is not identified either. Which of the two rejections lands
|
|
* depends on how wolfSSL returns a cert's public key, so accept both. */
|
|
AssertIntEQ(0, load_file("./keys/server-cert-ed25519.der",
|
|
&cert, &certSz));
|
|
ret = wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL);
|
|
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
|
|
AssertNull(out);
|
|
free(cert);
|
|
cert = NULL;
|
|
|
|
/* The PEM form makes that same DER first, so rejecting it must free it. */
|
|
AssertIntEQ(0, load_file("./keys/server-cert-ed25519.pem",
|
|
&cert, &certSz));
|
|
ret = wolfSSH_ReadCert_buffer(cert, certSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL);
|
|
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
|
|
AssertNull(out);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
free(cert);
|
|
cert = NULL;
|
|
#endif /* WOLFSSH_NO_ED25519 */
|
|
#endif /* WOLFSSH_CERTS */
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_ReadCert_file(void)
|
|
{
|
|
/* The arguments are checked ahead of any certificate, so those cases hold for
|
|
* an OpenSSH-only build too. */
|
|
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
|
|
byte* out = NULL;
|
|
word32 outSz = 0;
|
|
const byte* outType = NULL;
|
|
word32 outTypeSz = 0;
|
|
byte flavor = 0xFF;
|
|
byte stale[1];
|
|
|
|
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file(NULL,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_file("./keys/server-cert.pem",
|
|
NULL, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
|
|
/* A file that never opens still clears every out parameter. Sentinels go
|
|
in, so a stale pointer surviving the call would fail here. */
|
|
out = stale;
|
|
outSz = 0xDEADBEEF;
|
|
outType = stale;
|
|
outTypeSz = 0xDEADBEEF;
|
|
flavor = 0xFF;
|
|
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file("./keys/no-such-cert.pem",
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
/* A directory fails further in, opening but not reading. */
|
|
out = stale;
|
|
outSz = 0xDEADBEEF;
|
|
flavor = 0xFF;
|
|
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file("./keys",
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file("./keys/server-cert.pem",
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntEQ(out[0], 0x30);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
|
|
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
out = NULL;
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file("./keys/server-cert.der",
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntEQ(out[0], 0x30);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
#endif /* WOLFSSH_CERTS && !WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
|
#endif
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_CTX_UseCert_file(void)
|
|
{
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_FILESYSTEM) && \
|
|
!defined(WOLFSSH_USER_FILESYSTEM) && !defined(NO_WOLFSSH_SERVER)
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
int ret;
|
|
#endif
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_UseCert_file(NULL, "./keys/server-cert.pem"));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_CTX_UseCert_file(ctx, NULL));
|
|
AssertIntEQ(WS_BAD_FILE_E,
|
|
wolfSSH_CTX_UseCert_file(ctx, "./keys/no-such-cert.pem"));
|
|
|
|
/* Both encodings load through the same call. */
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert.pem"));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert.der"));
|
|
#endif
|
|
|
|
/* A CTX takes the certificate itself, not a public key line. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_UseCert_file(ctx, "./keys/id_ecdsa.pub"));
|
|
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
|
|
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
|
|
WSTRLEN(osshCertLine)));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_CTX_UseCert_file(ctx, osshCertPath));
|
|
AssertIntEQ(0, remove(osshCertPath));
|
|
#endif
|
|
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
/* Refused here too, on the same codes as wolfSSH_ReadCert_file(). */
|
|
ret = wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert-ed25519.der");
|
|
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
|
|
ret = wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert-ed25519.pem");
|
|
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
|
|
#endif
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_CTX_AddRootCert_file(void)
|
|
{
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_FILESYSTEM) && \
|
|
!defined(WOLFSSH_USER_FILESYSTEM) && !defined(NO_WOLFSSH_SERVER)
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_AddRootCert_file(NULL, "./keys/ca-cert-ecc.pem"));
|
|
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_CTX_AddRootCert_file(ctx, NULL));
|
|
AssertIntEQ(WS_BAD_FILE_E,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/no-such-ca.pem"));
|
|
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/ca-cert-ecc.pem"));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/ca-cert-ecc.der"));
|
|
|
|
/* A CTX takes the certificate itself, not a public key line. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/id_ecdsa.pub"));
|
|
|
|
/* The cert manager rejects a non-CA in wolfSSL's codes; this path maps. */
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/server-key-ecc.der"));
|
|
|
|
/* The buffer entry point, tested here because it shares this one's
|
|
* decoder: a PEM body that will not decode is a parse failure. */
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)badPemCert,
|
|
(word32)WSTRLEN(badPemCert), WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)zeroLenPemCert,
|
|
(word32)WSTRLEN(zeroLenPemCert), WOLFSSH_FORMAT_PEM));
|
|
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
|
|
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
|
|
WSTRLEN(osshCertLine)));
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_AddRootCert_file(ctx, osshCertPath));
|
|
AssertIntEQ(0, remove(osshCertPath));
|
|
#endif
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif
|
|
}
|
|
|
|
#else
|
|
|
|
static void test_wolfSSH_ReadCert_buffer(void) { ; }
|
|
static void test_wolfSSH_ReadCert_file(void) { ; }
|
|
static void test_wolfSSH_CTX_UseCert_file(void) { ; }
|
|
static void test_wolfSSH_CTX_AddRootCert_file(void) { ; }
|
|
|
|
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
|
|
|
|
|
|
static void test_wolfSSH_CTX_UsePrivateKey_buffer_pem(void)
|
|
{
|
|
#if !defined(NO_WOLFSSH_SERVER)
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
#ifdef WOLFSSH_CERTS
|
|
byte* key = NULL;
|
|
word32 keySz = 0;
|
|
#endif
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* The key files come in through load_file(), which certificate support
|
|
* carries, so the cases reading one are gated with it. */
|
|
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_RSA)
|
|
AssertIntEQ(0, load_file("./keys/server-key-rsa.pem", &key, &keySz));
|
|
AssertNotNull(key);
|
|
AssertIntNE(0, keySz);
|
|
|
|
/* PEM private key should load successfully */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, key, keySz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
|
|
free(key);
|
|
key = NULL;
|
|
#endif /* WOLFSSH_CERTS && !WOLFSSH_NO_RSA */
|
|
|
|
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_ECDSA)
|
|
AssertIntEQ(0, load_file("./keys/server-key-ecc.pem", &key, &keySz));
|
|
AssertNotNull(key);
|
|
AssertIntNE(0, keySz);
|
|
|
|
/* PEM ECC private key should load successfully */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, key, keySz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
|
|
free(key);
|
|
key = NULL;
|
|
#endif /* WOLFSSH_CERTS && !WOLFSSH_NO_ECDSA */
|
|
|
|
/* A body that will not decode and one that decodes to nothing are both
|
|
* parse failures, on a path that needs no certificate support. */
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, (const byte*)badPemKey,
|
|
(word32)WSTRLEN(badPemKey), WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx, (const byte*)zeroLenPemKey,
|
|
(word32)WSTRLEN(zeroLenPemKey), WOLFSSH_FORMAT_PEM));
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif /* NO_WOLFSSH_SERVER */
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_CTX_SetWindowPacketSize(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
|
|
/* NULL ctx must be rejected. */
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_SetWindowPacketSize(NULL, 0, 0));
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* Both zero: should default without error. */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx, 0, 0));
|
|
|
|
/* windowSz exactly at upper bound: must succeed and be stored. */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx, WINDOW_SZ_UPPER_BOUND, 0));
|
|
AssertIntEQ(WINDOW_SZ_UPPER_BOUND, (int)ctx->windowSz);
|
|
|
|
/* windowSz one above upper bound: must fail. */
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx,
|
|
WINDOW_SZ_UPPER_BOUND + 1, 0));
|
|
|
|
/* maxPacketSz exactly at the channel limit: must succeed and be stored. */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx, 0, MAX_CHANNEL_PACKET_SZ));
|
|
AssertIntEQ(MAX_CHANNEL_PACKET_SZ, (int)ctx->maxPacketSz);
|
|
|
|
/* maxPacketSz one above the channel limit: must fail. */
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx, 0, MAX_CHANNEL_PACKET_SZ + 1));
|
|
|
|
/* The transport limit itself does not fit once framing is added. */
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx, 0, MAX_PACKET_SZ));
|
|
|
|
/* Both valid non-zero values: must succeed and be stored. */
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_SetWindowPacketSize(ctx,
|
|
DEFAULT_WINDOW_SZ, DEFAULT_MAX_PACKET_SZ));
|
|
AssertIntEQ(DEFAULT_WINDOW_SZ, (int)ctx->windowSz);
|
|
AssertIntEQ(DEFAULT_MAX_PACKET_SZ, (int)ctx->maxPacketSz);
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
|
|
!defined(WOLFSSH_NO_ECDSA)
|
|
|
|
/* Joins two buffers so a multi-block PEM can be built in memory. Returns 0 on
|
|
* success. */
|
|
static int catBuffers(const byte* a, word32 aSz, const byte* b, word32 bSz,
|
|
byte** out, word32* outSz)
|
|
{
|
|
byte* buf;
|
|
int ret = -1;
|
|
|
|
*out = NULL;
|
|
*outSz = 0;
|
|
|
|
buf = (byte*)malloc(aSz + bSz);
|
|
if (buf != NULL) {
|
|
memcpy(buf, a, aSz);
|
|
memcpy(buf + aSz, b, bSz);
|
|
*out = buf;
|
|
*outSz = aSz + bSz;
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
|
|
|
|
/* Relabels a certificate PEM as the trusted form. The body is the plain
|
|
* certificate, without the trust settings OpenSSL appends, so this covers
|
|
* how the header is read rather than what follows it. Returns 0 on success. */
|
|
static int makeTrustedPem(const byte* pem, word32 pemSz, byte** out,
|
|
word32* outSz)
|
|
{
|
|
static const char begin[] = "-----BEGIN CERTIFICATE-----";
|
|
static const char end[] = "-----END CERTIFICATE-----";
|
|
static const char tBegin[] = "-----BEGIN TRUSTED CERTIFICATE-----";
|
|
static const char tEnd[] = "-----END TRUSTED CERTIFICATE-----\n";
|
|
const char* b;
|
|
const char* e;
|
|
byte* buf;
|
|
word32 bodySz;
|
|
word32 sz;
|
|
|
|
*out = NULL;
|
|
*outSz = 0;
|
|
|
|
b = WSTRNSTR((const char*)pem, begin, pemSz);
|
|
e = WSTRNSTR((const char*)pem, end, pemSz);
|
|
if (b == NULL || e == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
b += sizeof(begin) - 1;
|
|
if (e <= b) {
|
|
return -1;
|
|
}
|
|
bodySz = (word32)(e - b);
|
|
sz = (word32)(sizeof(tBegin) - 1) + bodySz + (word32)(sizeof(tEnd) - 1);
|
|
|
|
buf = (byte*)malloc(sz);
|
|
if (buf == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(buf, tBegin, sizeof(tBegin) - 1);
|
|
memcpy(buf + sizeof(tBegin) - 1, b, bodySz);
|
|
memcpy(buf + sizeof(tBegin) - 1 + bodySz, tEnd, sizeof(tEnd) - 1);
|
|
|
|
*out = buf;
|
|
*outSz = sz;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
|
|
|
|
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
|
|
|
|
|
|
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_ECDSA)
|
|
/* Build the length-prefixed single-cert chain buffer that
|
|
* wolfSSH_CERTMAN_VerifyCerts_buffer expects. Caller frees *chain. */
|
|
static int certman_make_chain(const byte* cert, word32 certSz,
|
|
byte** chain, word32* chainSz)
|
|
{
|
|
int ret = 0;
|
|
byte* buf;
|
|
|
|
buf = (byte*)malloc(UINT32_SZ + certSz);
|
|
if (buf != NULL) {
|
|
buf[0] = (byte)(certSz >> 24);
|
|
buf[1] = (byte)(certSz >> 16);
|
|
buf[2] = (byte)(certSz >> 8);
|
|
buf[3] = (byte)(certSz);
|
|
memcpy(buf + UINT32_SZ, cert, certSz);
|
|
*chain = buf;
|
|
*chainSz = UINT32_SZ + certSz;
|
|
}
|
|
else {
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* WOLFSSH_CERTS && !WOLFSSH_NO_ECDSA */
|
|
|
|
|
|
static void test_wolfSSH_CertMan(void)
|
|
{
|
|
#ifdef WOLFSSH_CERTMAN
|
|
/* This chunk of test is checking the innards of the WOLFSSH_CERTMAN
|
|
* struct which has a private declaration at the moment. */
|
|
{
|
|
WOLFSSH_CERTMAN* cm = NULL;
|
|
|
|
cm = wolfSSH_CERTMAN_new(NULL);
|
|
AssertNotNull(cm);
|
|
AssertNull(cm->heap);
|
|
|
|
wolfSSH_CERTMAN_free(cm);
|
|
}
|
|
{
|
|
WOLFSSH_CERTMAN cm;
|
|
WOLFSSH_CERTMAN* cmRef;
|
|
byte fakeHeap[32];
|
|
|
|
cmRef = wolfSSH_CERTMAN_init(&cm, NULL);
|
|
AssertNotNull(cmRef);
|
|
AssertNull(cmRef->heap);
|
|
|
|
cmRef = wolfSSH_CERTMAN_init(&cm, fakeHeap);
|
|
AssertNotNull(cmRef);
|
|
AssertNotNull(cmRef->heap);
|
|
AssertEQ(cmRef->heap, fakeHeap);
|
|
}
|
|
#endif /* WOLFSSH_CERTMAN */
|
|
|
|
#ifdef WOLFSSH_CERTS
|
|
{
|
|
/* VerifyCerts_buffer must reject certsCount == 0; otherwise the
|
|
* inner loops short-circuit and the function returns WS_SUCCESS
|
|
* without verifying anything. */
|
|
WOLFSSH_CERTMAN* cm;
|
|
unsigned char dummy[1] = { 0 };
|
|
|
|
cm = wolfSSH_CERTMAN_new(NULL);
|
|
AssertNotNull(cm);
|
|
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(cm, dummy, sizeof(dummy), 0));
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(NULL, dummy, sizeof(dummy), 1));
|
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(cm, NULL, 0, 1));
|
|
|
|
wolfSSH_CERTMAN_free(cm);
|
|
}
|
|
/* ECC trust anchor and leaf, so guard on ECDSA like the sibling tests. */
|
|
#ifndef WOLFSSH_NO_ECDSA
|
|
{
|
|
/* Negative control: a trusted CA presented as the leaf (index 0) must
|
|
* be rejected as an end-entity cert in both FPKI and non-FPKI builds;
|
|
* otherwise a trusted CA could be used to bypass authentication. */
|
|
WOLFSSH_CERTMAN* cm;
|
|
byte* caCert = NULL;
|
|
byte* chain = NULL;
|
|
word32 caCertSz = 0;
|
|
word32 chainSz;
|
|
|
|
cm = wolfSSH_CERTMAN_new(NULL);
|
|
AssertNotNull(cm);
|
|
|
|
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.der", &caCert, &caCertSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CERTMAN_LoadRootCA_buffer(cm, caCert, caCertSz));
|
|
|
|
AssertIntEQ(0, certman_make_chain(caCert, caCertSz, &chain, &chainSz));
|
|
AssertIntEQ(WS_CERT_PROFILE_E,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 1));
|
|
|
|
free(chain);
|
|
free(caCert);
|
|
wolfSSH_CERTMAN_free(cm);
|
|
}
|
|
#ifdef WOLFSSH_NO_FPKI
|
|
{
|
|
/* Positive control: a genuine end-entity leaf signed by the CA still
|
|
* verifies. The leaf is not an FPKI cert, so only assert this when
|
|
* FPKI profile checking is compiled out. */
|
|
WOLFSSH_CERTMAN* cm;
|
|
byte* caCert = NULL;
|
|
byte* leafCert = NULL;
|
|
byte* chain = NULL;
|
|
word32 caCertSz = 0;
|
|
word32 leafCertSz = 0;
|
|
word32 chainSz;
|
|
|
|
cm = wolfSSH_CERTMAN_new(NULL);
|
|
AssertNotNull(cm);
|
|
|
|
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.der", &caCert, &caCertSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CERTMAN_LoadRootCA_buffer(cm, caCert, caCertSz));
|
|
|
|
AssertIntEQ(0,
|
|
load_file("./keys/fred-cert.der", &leafCert, &leafCertSz));
|
|
AssertIntEQ(0,
|
|
certman_make_chain(leafCert, leafCertSz, &chain, &chainSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(cm, chain, chainSz, 1));
|
|
|
|
free(chain);
|
|
free(caCert);
|
|
free(leafCert);
|
|
wolfSSH_CERTMAN_free(cm);
|
|
}
|
|
#endif /* WOLFSSH_NO_FPKI */
|
|
#endif /* WOLFSSH_NO_ECDSA */
|
|
#endif /* WOLFSSH_CERTS */
|
|
}
|
|
|
|
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
|
|
!defined(WOLFSSH_NO_ECDSA)
|
|
|
|
/* The CA signed fred's certificate, so a chain check tells an installed CA
|
|
* from a missing one. */
|
|
static void assertCaInstalled(WOLFSSH_CTX* ctx)
|
|
{
|
|
byte* leafDer = NULL;
|
|
byte* chain = NULL;
|
|
word32 leafDerSz = 0;
|
|
word32 chainSz = 0;
|
|
|
|
AssertIntEQ(0, load_file("./keys/fred-cert.der", &leafDer, &leafDerSz));
|
|
AssertIntEQ(0, certman_make_chain(leafDer, leafDerSz, &chain, &chainSz));
|
|
#ifdef WOLFSSH_NO_FPKI
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(ctx->certMan, chain, chainSz, 1));
|
|
#else
|
|
/* An FPKI build rejects this leaf's profile, but only after finding its
|
|
* signer, so the code still tells the CA apart from a missing one. */
|
|
AssertIntEQ(WS_CERT_PROFILE_E,
|
|
wolfSSH_CERTMAN_VerifyCerts_buffer(ctx->certMan, chain, chainSz, 1));
|
|
#endif
|
|
free(chain);
|
|
free(leafDer);
|
|
}
|
|
|
|
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
|
|
|
|
|
|
/* A CA buffer may hold a bundle, so every PEM block in it is loaded. A block
|
|
* that will not load is skipped rather than failing the bundle. */
|
|
static void test_wolfSSH_CTX_AddRootCert_bundle(void)
|
|
{
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
|
|
!defined(WOLFSSH_NO_ECDSA)
|
|
static const char junk[] = "Bag Attributes: not a certificate\n";
|
|
static const char badPem[] =
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
"$$$$ not base64 $$$$\n"
|
|
"-----END CERTIFICATE-----\n";
|
|
/* Valid base64, but no certificate, so the manager is what refuses it. */
|
|
static const char notACertPem[] =
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
"bm90IGEgY2VydGlmaWNhdGUgYXQgYWxsLCBqdXN0IHRleHQ=\n"
|
|
"-----END CERTIFICATE-----\n";
|
|
/* A header with nothing closing it, so the walk has to resume from behind
|
|
* it rather than let it swallow the block that follows. */
|
|
static const char headerOnlyPem[] =
|
|
"-----BEGIN CERTIFICATE-----\n";
|
|
/* Same block behind text holding a NUL. */
|
|
static const char nulBadPem[] =
|
|
"Bag Attributes\0more text\n"
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
"$$$$ not base64 $$$$\n"
|
|
"-----END CERTIFICATE-----\n";
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH_CTX* ctxOne = NULL;
|
|
byte* ca = NULL;
|
|
byte* leaf = NULL;
|
|
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
|
|
byte* trusted = NULL;
|
|
byte* trustedLeaf = NULL;
|
|
word32 trustedSz = 0;
|
|
word32 trustedLeafSz = 0;
|
|
#endif
|
|
byte* bundle = NULL;
|
|
word32 caSz = 0;
|
|
word32 leafSz = 0;
|
|
word32 bundleSz = 0;
|
|
|
|
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.pem", &ca, &caSz));
|
|
AssertIntEQ(0, load_file("./keys/server-cert.pem", &leaf, &leafSz));
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* The CA is the second block, so anything it signs verifies only if the
|
|
* walk got past the first. */
|
|
AssertIntEQ(0, catBuffers(leaf, leafSz, ca, caSz, &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctx);
|
|
|
|
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
|
|
/* A trusted-certificate block names a CA too, alone and beside a plain
|
|
* one. */
|
|
AssertIntEQ(0, makeTrustedPem(ca, caSz, &trusted, &trustedSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, trusted, trustedSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
|
|
/* A presented certificate carries no trust data, so the certificate
|
|
* path declines the form however the caller reached it. A plain block
|
|
* ahead of a trusted one is the certificate, so it is still taken. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, trusted, trustedSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(0, catBuffers(leaf, leafSz, trusted, trustedSz, &bundle,
|
|
&bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UseCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
AssertIntEQ(0, catBuffers(trusted, trustedSz, ca, caSz, &bundle,
|
|
&bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
/* Two trusted blocks with the CA second, so only a walk that looks for
|
|
* a trusted header again after passing the first installs it. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, makeTrustedPem(leaf, leafSz, &trustedLeaf, &trustedLeafSz));
|
|
AssertIntEQ(0, catBuffers(trustedLeaf, trustedLeafSz, trusted, trustedSz,
|
|
&bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
|
|
/* A trusted block behind a bad plain one. Only a walk that stepped over
|
|
* the failure reaches it, and installing the CA is what shows it did. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
|
|
trusted, trustedSz, &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
|
|
|
|
/* The CA leads this bundle rather than closing it, so a walk that kept
|
|
* only the last block would leave the leaf without a signer. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, catBuffers(ca, caSz, leaf, leafSz, &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
|
|
/* A block the manager turns down leaves the ones around it installed. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, catBuffers((const byte*)notACertPem,
|
|
(word32)(sizeof(notACertPem) - 1), ca, caSz, &bundle,
|
|
&bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
|
|
/* An unterminated header ahead of the CA. The block it opens has no end,
|
|
* so only a walk that steps past the header alone reaches the CA. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, catBuffers((const byte*)headerOnlyPem,
|
|
(word32)(sizeof(headerOnlyPem) - 1), ca, caSz, &bundle,
|
|
&bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
|
|
/* A block that will not decode is skipped wherever it sits, and the walk
|
|
* carries on from behind its header. */
|
|
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctxOne);
|
|
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
|
|
ca, caSz, &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
assertCaInstalled(ctxOne);
|
|
wolfSSH_CTX_free(ctxOne);
|
|
|
|
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)badPem,
|
|
(word32)(sizeof(badPem) - 1), &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
/* A NUL is not the end of the buffer, so the bad block behind it is
|
|
* still read. */
|
|
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)nulBadPem,
|
|
(word32)(sizeof(nulBadPem) - 1), &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
/* Text around the blocks is not a certificate, so it is stepped over. */
|
|
AssertIntEQ(0, catBuffers((const byte*)junk, (word32)(sizeof(junk) - 1),
|
|
ca, caSz, &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)junk,
|
|
(word32)(sizeof(junk) - 1), &bundle, &bundleSz));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
bundle = NULL;
|
|
|
|
/* Nothing installed is what fails the call. Blocks that all fail are a
|
|
* parse error, and a buffer holding no block at all is a bad file. */
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)badPem,
|
|
(word32)(sizeof(badPem) - 1), WOLFSSH_FORMAT_PEM));
|
|
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
|
|
(const byte*)notACertPem, (word32)(sizeof(notACertPem) - 1),
|
|
&bundle, &bundleSz));
|
|
AssertIntEQ(WS_PARSE_E,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
|
|
WOLFSSH_FORMAT_PEM));
|
|
free(bundle);
|
|
AssertIntEQ(WS_BAD_FILE_E,
|
|
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)junk,
|
|
(word32)(sizeof(junk) - 1), WOLFSSH_FORMAT_PEM));
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
free(ca);
|
|
free(leaf);
|
|
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
|
|
free(trusted);
|
|
free(trustedLeaf);
|
|
#endif
|
|
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
|
|
}
|
|
|
|
|
|
/* A CA file may be in the trusted form, which the certificate path still
|
|
* declines. */
|
|
static void test_wolfSSH_CTX_AddRootCert_file_trusted(void)
|
|
{
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
|
|
!defined(WOLFSSH_NO_ECDSA) && defined(WOLFSSH_HAVE_TRUSTED_CERT_PEM) && \
|
|
!defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
|
|
static const char trustedPath[] = "./trusted-ca-test.pem";
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WFILE* fp = NULL;
|
|
byte* ca = NULL;
|
|
byte* trusted = NULL;
|
|
word32 caSz = 0;
|
|
word32 trustedSz = 0;
|
|
|
|
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.pem", &ca, &caSz));
|
|
AssertIntEQ(0, makeTrustedPem(ca, caSz, &trusted, &trustedSz));
|
|
|
|
AssertIntEQ(WFOPEN(NULL, &fp, trustedPath, "wb"), 0);
|
|
AssertNotNull(fp);
|
|
AssertIntEQ((int)WFWRITE(NULL, trusted, 1, trustedSz, fp), (int)trustedSz);
|
|
WFCLOSE(NULL, fp);
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_CTX_AddRootCert_file(ctx, trustedPath));
|
|
assertCaInstalled(ctx);
|
|
|
|
/* A presented certificate carries no trust data, so this form is not one
|
|
* of the certificate path's. */
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_CTX_UseCert_file(ctx, trustedPath));
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
AssertIntEQ(0, remove(trustedPath));
|
|
free(trusted);
|
|
free(ca);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Trust settings say what a root may be trusted for, which means nothing in
|
|
* a certificate sent to a peer, so the readers name the form and decline it. */
|
|
static void test_wolfSSH_ReadCert_buffer_trusted(void)
|
|
{
|
|
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
|
|
!defined(WOLFSSH_NO_ECDSA) && defined(WOLFSSH_HAVE_TRUSTED_CERT_PEM)
|
|
byte* cert = NULL;
|
|
byte* trusted = NULL;
|
|
byte* out = NULL;
|
|
const byte* outType = NULL;
|
|
word32 certSz = 0;
|
|
word32 trustedSz = 0;
|
|
word32 outSz = 0;
|
|
word32 outTypeSz = 0;
|
|
byte flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
|
|
|
|
AssertIntEQ(0, load_file("./keys/server-cert.pem", &cert, &certSz));
|
|
AssertIntEQ(0, makeTrustedPem(cert, certSz, &trusted, &trustedSz));
|
|
|
|
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(trusted, trustedSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNull(out);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
|
|
free(trusted);
|
|
free(cert);
|
|
#endif
|
|
}
|
|
|
|
|
|
#define KEY_BUF_SZ 2048
|
|
|
|
#ifndef WOLFSSH_NO_RSA
|
|
|
|
const char id_rsa[] =
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n"
|
|
"NhAAAAAwEAAQAAAQEAy2cigZDlpBT+X2MJHAoHnfeFf6+LHm6BDkAT8V9ejHA4dY0Aepb6\n"
|
|
"NbV6u/oYZlueKPeAZ3GNztR9szoL6FSlMvkd9oqvfoxjTGu71T0981ybJelqqGATGtevHU\n"
|
|
"6Jko/I0+lgSQFKWQJ7D3Dj2zlZpIXB2Q7xl/i9kFZgaIqFhUHdWO9JMOwCFwoDrhd8v5xk\n"
|
|
"y1v3OIIZDxiYxVIKbf2J07WbwiSFAxXfiX8TjUBDLFmtqt1AF6LjAyGyaRICXkaGJQ/QJ9\n"
|
|
"sX85h9bkiPlGNAtQGQtNUg3tC9GqOkZ9tCKY1Efh/r0zosOA7ufxg6ymLpq1C4LU/4ENGH\n"
|
|
"kuRPAKvu8wAAA8gztJfmM7SX5gAAAAdzc2gtcnNhAAABAQDLZyKBkOWkFP5fYwkcCged94\n"
|
|
"V/r4seboEOQBPxX16McDh1jQB6lvo1tXq7+hhmW54o94BncY3O1H2zOgvoVKUy+R32iq9+\n"
|
|
"jGNMa7vVPT3zXJsl6WqoYBMa168dTomSj8jT6WBJAUpZAnsPcOPbOVmkhcHZDvGX+L2QVm\n"
|
|
"BoioWFQd1Y70kw7AIXCgOuF3y/nGTLW/c4ghkPGJjFUgpt/YnTtZvCJIUDFd+JfxONQEMs\n"
|
|
"Wa2q3UAXouMDIbJpEgJeRoYlD9An2xfzmH1uSI+UY0C1AZC01SDe0L0ao6Rn20IpjUR+H+\n"
|
|
"vTOiw4Du5/GDrKYumrULgtT/gQ0YeS5E8Aq+7zAAAAAwEAAQAAAQEAvbdBiQXkGyn1pHST\n"
|
|
"/5IfTqia3OCX6td5ChicQUsJvgXBs2rDopQFZmkRxBjd/0K+/0jyfAl/EgZCBBRFHPsuZp\n"
|
|
"/S4ayzSV6aE6J8vMT1bnLWxwKyl7+csjGwRK6HRKtVzsnjI9TPSrw0mc9ax5PzV6/mgZUd\n"
|
|
"o/i+nszh+UASj5mYrBGqMiINspzX6YC+qoUHor3rEJOd9p1aO+N5+1fDKiDnlkM5IO0Qsz\n"
|
|
"GktuwL0fzv9zBnGfnWVJz3CorfP1OW5KCtrDn7BnkQf1eBeVLzq/uoglUjS4DNnVfLA67D\n"
|
|
"O4ZfwtnoW8Gr2R+KdvnypvHnDeY5X51r5PDgL4+7z47pWQAAAIBNFcAzHHE19ISGN8YRHk\n"
|
|
"23/r/3zfvzHU68GSKR1Xj/Y4LSdRTpSm3wBrdQ17f5B4V7RVl2CJvoPekTggnBDQlLJ7fU\n"
|
|
"NU93/nZrY9teYdrNh03buL54VVb5tUM+KN+27zERlTj0/LmYJupN97sZXmlgKsvLbcsnM2\n"
|
|
"i7HuQQaFnsIQAAAIEA5wqFVatT9yovt8pS7rAyYUL/cqc50TZ/5Nwfy5uasRyf1BphHwEW\n"
|
|
"LEimBemVc+VrNwAkt6MFWuloK5ssqb1ubvtRI8Mntd15rRfZtq/foS3J8FJxueXLDWlECy\n"
|
|
"PmVyfVN1Vv4ZeirBy9BTYLiSuxMes+HYks3HucQhxIN1j8SA0AAACBAOFgRjfWXv1/93Jp\n"
|
|
"6CCJ5c98MWP+zu1FbLIlklxPb85osZqlazXHNPPEtblC4z+OqRGMCsv2683anU4ZzcTFIk\n"
|
|
"JS3lzeJ3tdAH4osQ5etKkV4mcdCmeRpjudB9VbaziVhPX02qkPWpM0ckPrgB3hVNUDPz89\n"
|
|
"GtJd3mlhyY5IfFL/AAAADWJvYkBsb2NhbGhvc3QBAgMEBQ==\n"
|
|
"-----END OPENSSH PRIVATE KEY-----\n";
|
|
|
|
const char id_rsa_pub[] =
|
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLZyKBkOWkFP5fYwkcCged94V/r4seboEO"
|
|
"QBPxX16McDh1jQB6lvo1tXq7+hhmW54o94BncY3O1H2zOgvoVKUy+R32iq9+jGNMa7vVPT3z"
|
|
"XJsl6WqoYBMa168dTomSj8jT6WBJAUpZAnsPcOPbOVmkhcHZDvGX+L2QVmBoioWFQd1Y70kw"
|
|
"7AIXCgOuF3y/nGTLW/c4ghkPGJjFUgpt/YnTtZvCJIUDFd+JfxONQEMsWa2q3UAXouMDIbJp"
|
|
"EgJeRoYlD9An2xfzmH1uSI+UY0C1AZC01SDe0L0ao6Rn20IpjUR+H+vTOiw4Du5/GDrKYumr"
|
|
"ULgtT/gQ0YeS5E8Aq+7z bob@localhost\n";
|
|
|
|
#endif /* WOLFSSH_NO_RSA */
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
|
|
const char id_ecdsa[] =
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n"
|
|
"1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTAqdBgCp8bYSq2kQQ48/Ud8Iy6Mjnb\n"
|
|
"/fpB3LfSE/1kx9VaaE4FL3i9Gg2vDV0eLGM3PWksFNPhULxtcYJyjaBjAAAAqJAeleSQHp\n"
|
|
"XkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMCp0GAKnxthKraR\n"
|
|
"BDjz9R3wjLoyOdv9+kHct9IT/WTH1VpoTgUveL0aDa8NXR4sYzc9aSwU0+FQvG1xgnKNoG\n"
|
|
"MAAAAgPrOgktioNqad/wHNC/rt/zVrpNqDnOwg9tNDFMOTwo8AAAANYm9iQGxvY2FsaG9z\n"
|
|
"dAECAw==\n"
|
|
"-----END OPENSSH PRIVATE KEY-----\n";
|
|
|
|
const char id_ecdsa_pub[] =
|
|
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABB"
|
|
"BMCp0GAKnxthKraRBDjz9R3wjLoyOdv9+kHct9IT/WTH1VpoTgUveL0aDa8NXR4sYzc9aSwU"
|
|
"0+FQvG1xgnKNoGM= bob@localhost\n";
|
|
|
|
/* Same as id_ecdsa but with the last pad byte changed from 0x03 to 0x04,
|
|
* so the padding sequence 1,2,3 is broken at position 3. */
|
|
const char id_ecdsa_bad_pad[] =
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n"
|
|
"1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTAqdBgCp8bYSq2kQQ48/Ud8Iy6Mjnb\n"
|
|
"/fpB3LfSE/1kx9VaaE4FL3i9Gg2vDV0eLGM3PWksFNPhULxtcYJyjaBjAAAAqJAeleSQHp\n"
|
|
"XkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMCp0GAKnxthKraR\n"
|
|
"BDjz9R3wjLoyOdv9+kHct9IT/WTH1VpoTgUveL0aDa8NXR4sYzc9aSwU0+FQvG1xgnKNoG\n"
|
|
"MAAAAgPrOgktioNqad/wHNC/rt/zVrpNqDnOwg9tNDFMOTwo8AAAANYm9iQGxvY2FsaG9z\n"
|
|
"dAECBA==\n"
|
|
"-----END OPENSSH PRIVATE KEY-----\n";
|
|
|
|
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
|
|
|
static void test_wolfSSH_ReadKey(void)
|
|
{
|
|
#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
|
|
byte *key, *keyCheck, *derKey;
|
|
const byte* keyType;
|
|
word32 keySz, keyTypeSz, derKeySz;
|
|
int ret;
|
|
#endif
|
|
|
|
#ifndef WOLFSSH_NO_RSA
|
|
|
|
/* OpenSSH Format, ssh-rsa, private, need alloc */
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa, (word32)WSTRLEN(id_rsa),
|
|
WOLFSSH_FORMAT_OPENSSH, &key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ssh-rsa"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
|
|
/* SSL PEM Format, ssh-rsa, private, need alloc */
|
|
derKey = NULL;
|
|
derKeySz = 0;
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = ConvertHexToBin(serverKeyRsaDer, &derKey, &derKeySz,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
AssertIntEQ(ret, 0);
|
|
ret = wolfSSH_ReadKey_buffer(derKey, derKeySz, WOLFSSH_FORMAT_ASN1,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ssh-rsa"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
WFREE(derKey, NULL, 0);
|
|
|
|
/* OpenSSH Format, ssh-rsa, public, need alloc */
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa_pub,
|
|
(word32)WSTRLEN(id_rsa_pub), WOLFSSH_FORMAT_SSH,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ssh-rsa"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
|
|
/* OpenSSH Format, ssh-rsa, private, no alloc */
|
|
keyCheck = (byte*)WMALLOC(KEY_BUF_SZ, NULL, DYNTYPE_FILE);
|
|
AssertNotNull(keyCheck);
|
|
key = keyCheck;
|
|
keySz = KEY_BUF_SZ;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa, (word32)WSTRLEN(id_rsa),
|
|
WOLFSSH_FORMAT_OPENSSH, &key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertTrue(key == keyCheck);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ssh-rsa"));
|
|
WFREE(keyCheck, NULL, DYNTYPE_FILE);
|
|
|
|
#endif /* WOLFSSH_NO_RSA */
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
|
|
/* OpenSSH Format, ecdsa-sha2-nistp256, private, need alloc */
|
|
(void)keyCheck;
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_ecdsa,
|
|
(word32)WSTRLEN(id_ecdsa), WOLFSSH_FORMAT_OPENSSH,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ecdsa-sha2-nistp256");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ecdsa-sha2-nistp256"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
|
|
/* SSL DER Format, ecdsa-sha2-nistp256, private, need alloc */
|
|
derKey = NULL;
|
|
derKeySz = 0;
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = ConvertHexToBin(serverKeyEccDer, &derKey, &derKeySz,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
ret = wolfSSH_ReadKey_buffer(derKey, derKeySz, WOLFSSH_FORMAT_ASN1,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ecdsa-sha2-nistp256");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ecdsa-sha2-nistp256"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
WFREE(derKey, NULL, 0);
|
|
|
|
/* OpenSSH Format, ecdsa-sha2-nistp256, public, need alloc */
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_ecdsa_pub,
|
|
(word32)WSTRLEN(id_ecdsa_pub), WOLFSSH_FORMAT_SSH,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertStrEQ(keyType, "ecdsa-sha2-nistp256");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ecdsa-sha2-nistp256"));
|
|
WFREE(key, NULL, DYNTYPE_FILE);
|
|
|
|
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
|
}
|
|
|
|
|
|
#ifndef WOLFSSH_NO_RSA
|
|
/* SubjectPublicKeyInfo for the same RSA key as serverKeyRsaDer. */
|
|
static const char serverKeyRsaPubPem[] =
|
|
"-----BEGIN PUBLIC KEY-----\n"
|
|
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2l2tJRR2FVnzQP08uGIw\n"
|
|
"s23A+ezsi4MenkKcykFq04rhUjTgDRNiftQPrlxNBPGN+sWtd6paBcrv+I2r/4op\n"
|
|
"CUwEwvUZy+0fsbQp08NsqSPfo6DlCN6tjHH5NIhs7Tvwb6UPrFn/azPxcPuMpLNF\n"
|
|
"Io2dd3rlKV+EFNmZ6urOLVHz41j6WwIPybUqvLJe08Iwuzyxw+9Y81CUKIvEZUr3\n"
|
|
"ANmX2WtNjZWhimIGtFARIoO06irn0KggR0//Rq7FE+E4i/hUrzpNL/gf14SQ2JMF\n"
|
|
"BsJ9kNvjnNDEZVoDrQCsWqLN2j+JWDdTvytGeqyJQStaLuh2517jKYWjY+rmhmB8\n"
|
|
"LQIDAQAB\n"
|
|
"-----END PUBLIC KEY-----\n";
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
/* The same SubjectPublicKeyInfo in DER, what the PEM above decodes to. */
|
|
static const char serverKeyRsaPubDer[] =
|
|
"30820122300d06092a864886f70d01010105000382010f003082010a02820101"
|
|
"00da5dad2514761559f340fd3cb86230b36dc0f9ecec8b831e9e429cca416ad3"
|
|
"8ae15234e00d13627ed40fae5c4d04f18dfac5ad77aa5a05caeff88dabff8a29"
|
|
"094c04c2f519cbed1fb1b429d3c36ca923dfa3a0e508dead8c71f934886ced3b"
|
|
"f06fa50fac59ff6b33f170fb8ca4b345228d9d777ae5295f8414d999eaeace2d"
|
|
"51f3e358fa5b020fc9b52abcb25ed3c230bb3cb1c3ef58f35094288bc4654af7"
|
|
"00d997d96b4d8d95a18a6206b450112283b4ea2ae7d0a820474fff46aec513e1"
|
|
"388bf854af3a4d2ff81fd78490d8930506c27d90dbe39cd0c4655a03ad00ac5a"
|
|
"a2cdda3f89583753bf2b467aac89412b5a2ee876e75ee32985a363eae686607c"
|
|
"2d0203010001";
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
#endif
|
|
|
|
|
|
static void test_wolfSSH_ReadPublicKey_pem(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_RSA
|
|
/* DoPemKey() used to pass a literal 1 for isPrivate when identifying the
|
|
* decoded key, so a public PEM converted by wc_PubKeyPemToDer() was then
|
|
* run through the private-key decoders and never identified. */
|
|
byte* key = NULL;
|
|
word32 keySz = 0;
|
|
const byte* keyType = NULL;
|
|
word32 keyTypeSz = 0;
|
|
word32 pemSz = (word32)WSTRLEN(serverKeyRsaPubPem);
|
|
int ret;
|
|
|
|
ret = wolfSSH_ReadPublicKey_buffer((const byte*)serverKeyRsaPubPem, pemSz,
|
|
WOLFSSH_FORMAT_PEM, &key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
|
|
#ifdef WOLFSSH_TPM
|
|
/* The public PEM branch of DoPemKey() is only compiled in with TPM
|
|
* support, so this is the only build where the read can succeed. */
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(key);
|
|
AssertIntGT(keySz, 0);
|
|
AssertIntLT(keySz, pemSz); /* DER is smaller than the PEM it came from */
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
AssertIntEQ(keyTypeSz, (word32)WSTRLEN("ssh-rsa"));
|
|
|
|
/* The output is the decoded SubjectPublicKeyInfo. Re-reading it as ASN.1
|
|
* confirms a public key came back, not a mis-tagged private one. */
|
|
{
|
|
byte* sshKey = NULL;
|
|
word32 sshKeySz = 0;
|
|
const byte* sshKeyType = NULL;
|
|
word32 sshKeyTypeSz = 0;
|
|
|
|
AssertIntEQ(wolfSSH_ReadPublicKey_buffer(key, keySz,
|
|
WOLFSSH_FORMAT_ASN1, &sshKey, &sshKeySz,
|
|
&sshKeyType, &sshKeyTypeSz, NULL), WS_SUCCESS);
|
|
AssertNotNull(sshKey);
|
|
AssertStrEQ(sshKeyType, "ssh-rsa");
|
|
WFREE(sshKey, NULL, DYNTYPE_PRIVKEY);
|
|
}
|
|
|
|
WFREE(key, NULL, DYNTYPE_PRIVKEY);
|
|
#else
|
|
/* Without TPM support the branch is compiled out and the read must fail
|
|
* cleanly, leaving the outputs untouched. */
|
|
AssertIntEQ(ret, WS_PARSE_E);
|
|
AssertNull(key);
|
|
AssertIntEQ(keySz, 0);
|
|
AssertNull(keyType);
|
|
AssertIntEQ(keyTypeSz, 0);
|
|
#endif /* WOLFSSH_TPM */
|
|
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
/* The public PEM branch above only builds with TPM support, so pin the
|
|
* behavior it depends on directly: the decoded SubjectPublicKeyInfo
|
|
* identifies as ssh-rsa only when isPrivate is 0. Hardcoding 1, as
|
|
* DoPemKey() once did, cannot identify it. IdentifyAsn1Key is
|
|
* WOLFSSH_LOCAL, so this only links against the test library. */
|
|
{
|
|
byte* der = NULL;
|
|
word32 derSz = 0;
|
|
|
|
AssertIntEQ(ConvertHexToBin(serverKeyRsaPubDer, &der, &derSz,
|
|
NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL), 0);
|
|
AssertIntEQ(IdentifyAsn1Key(der, derSz, 0, NULL, NULL), ID_SSH_RSA);
|
|
AssertIntLT(IdentifyAsn1Key(der, derSz, 1, NULL, NULL), 0);
|
|
WFREE(der, NULL, 0);
|
|
}
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
#endif /* WOLFSSH_NO_RSA */
|
|
}
|
|
|
|
|
|
#if defined(WOLFSSH_TPM) && !defined(NO_FILESYSTEM) && \
|
|
!defined(NO_WRITE_TEMP_FILES) && !defined(WOLFSSH_USER_FILESYSTEM)
|
|
|
|
static const char tpmKeyLine[] =
|
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDf5tsL7sT2wGvXbT2mNBOgnkO";
|
|
|
|
/* Stages sz bytes of buf at name. Returns 0 on success. */
|
|
static int tpmWriteKeyFile(const char* name, const char* buf, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
int ret = 0;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "wb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFWRITE(NULL, buf, 1, sz, fp) != sz)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* LoadTpmSshKey() appends " <user>\n" plus a NUL. Without a trailing newline
|
|
* the trim loop reclaims nothing, so the NUL runs past an undersized buffer.
|
|
* That byte lands in allocator slack: only a sanitizer build fails here. */
|
|
static void test_LoadTpmSshKey_NoTrailingNewline(void)
|
|
{
|
|
const char keyPath[] = "./tpm-key-line.tmp";
|
|
char trailing[sizeof(tpmKeyLine) + 1];
|
|
char expected[sizeof(tpmKeyLine) + 8];
|
|
char* line = NULL;
|
|
|
|
WSNPRINTF(expected, sizeof(expected), "%s hansel\n", tpmKeyLine);
|
|
WSNPRINTF(trailing, sizeof(trailing), "%s\n", tpmKeyLine);
|
|
|
|
AssertIntEQ(tpmWriteKeyFile(keyPath, tpmKeyLine,
|
|
(word32)WSTRLEN(tpmKeyLine)), 0);
|
|
line = LoadTpmSshKey(keyPath, "hansel");
|
|
AssertNotNull(line);
|
|
AssertStrEQ(line, expected);
|
|
WFREE(line, NULL, DYNTYPE_BUFFER);
|
|
line = NULL;
|
|
/* WREMOVE is only defined for SCP, SFTP and SSHD builds. */
|
|
AssertIntEQ(0, remove(keyPath));
|
|
|
|
/* The trimmed path always fit; confirm it yields the same line. */
|
|
AssertIntEQ(tpmWriteKeyFile(keyPath, trailing,
|
|
(word32)WSTRLEN(trailing)), 0);
|
|
line = LoadTpmSshKey(keyPath, "hansel");
|
|
AssertNotNull(line);
|
|
AssertStrEQ(line, expected);
|
|
WFREE(line, NULL, DYNTYPE_BUFFER);
|
|
line = NULL;
|
|
AssertIntEQ(0, remove(keyPath));
|
|
|
|
/* An empty file drives the same sizing with a length of 0. */
|
|
AssertIntEQ(tpmWriteKeyFile(keyPath, tpmKeyLine, 0), 0);
|
|
line = LoadTpmSshKey(keyPath, "hansel");
|
|
AssertNotNull(line);
|
|
AssertStrEQ(line, " hansel\n");
|
|
WFREE(line, NULL, DYNTYPE_BUFFER);
|
|
AssertIntEQ(0, remove(keyPath));
|
|
|
|
AssertNull(LoadTpmSshKey(keyPath, "hansel"));
|
|
}
|
|
|
|
#endif /* WOLFSSH_TPM && FILESYSTEM && !USER_FILESYSTEM */
|
|
|
|
#if defined(WOLFSSH_TPM) && defined(WOLFSSH_TEST_INTERNAL)
|
|
|
|
/* The key type is read with GetStringRef(), which sets the length from the
|
|
* wire but leaves the pointer alone when the name runs past the buffer. */
|
|
static void test_GetOpenSshPublicKey_type(void)
|
|
{
|
|
/* "ssh" carrying a length of 7. */
|
|
static const byte truncType[] = {
|
|
0x00, 0x00, 0x00, 0x07, 's', 's', 'h'
|
|
};
|
|
/* Too short to hold the length prefix. */
|
|
static const byte truncLen[] = { 0x00, 0x00 };
|
|
/* Parses, but names no key. */
|
|
static const byte emptyType[] = { 0x00, 0x00, 0x00, 0x00 };
|
|
/* A known SSH key type that wolfSSH's NameIdMap does not carry. */
|
|
static const byte unsupportedType[] = {
|
|
0x00, 0x00, 0x00, 0x07, 's', 's', 'h', '-', 'd', 's', 's'
|
|
};
|
|
#ifndef WOLFSSH_NO_RSA
|
|
/* string "ssh-rsa", mpint e, mpint n. */
|
|
static const byte rsaKey[] = {
|
|
0x00, 0x00, 0x00, 0x07, 's', 's', 'h', '-', 'r', 's', 'a',
|
|
0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x09,
|
|
0x00, 0xC5, 0x1A, 0x37, 0x8B, 0x42, 0x9D, 0xE0, 0x6F
|
|
};
|
|
#endif
|
|
WS_KeySignature keySig;
|
|
word32 idx;
|
|
|
|
WMEMSET(&keySig, 0, sizeof(keySig));
|
|
|
|
/* On failure idx keeps whatever was consumed, as elsewhere in the tree. */
|
|
idx = 0;
|
|
AssertIntEQ(GetOpenSshPublicKey(&keySig, truncType,
|
|
(word32)sizeof(truncType), &idx), WS_BUFFER_E);
|
|
AssertIntEQ(idx, UINT32_SZ);
|
|
|
|
idx = 0;
|
|
AssertIntEQ(GetOpenSshPublicKey(&keySig, truncLen,
|
|
(word32)sizeof(truncLen), &idx), WS_BUFFER_E);
|
|
AssertIntEQ(idx, 0);
|
|
|
|
idx = 0;
|
|
AssertIntEQ(GetOpenSshPublicKey(&keySig, emptyType,
|
|
(word32)sizeof(emptyType), &idx), WS_UNIMPLEMENTED_E);
|
|
AssertIntEQ(idx, (word32)sizeof(emptyType));
|
|
|
|
idx = 0;
|
|
AssertIntEQ(GetOpenSshPublicKey(&keySig, unsupportedType,
|
|
(word32)sizeof(unsupportedType), &idx), WS_UNIMPLEMENTED_E);
|
|
AssertIntEQ(idx, (word32)sizeof(unsupportedType));
|
|
|
|
#ifndef WOLFSSH_NO_RSA
|
|
idx = 0;
|
|
AssertIntEQ(wc_InitRsaKey(&keySig.ks.rsa.key, NULL), 0);
|
|
AssertIntEQ(GetOpenSshPublicKey(&keySig, rsaKey,
|
|
(word32)sizeof(rsaKey), &idx), WS_SUCCESS);
|
|
AssertIntEQ(idx, (word32)sizeof(rsaKey));
|
|
AssertIntEQ(wc_FreeRsaKey(&keySig.ks.rsa.key), 0);
|
|
#endif
|
|
}
|
|
|
|
#endif /* WOLFSSH_TPM && WOLFSSH_TEST_INTERNAL */
|
|
|
|
|
|
static void test_wolfSSH_ReadKey_badPad(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
byte* key = NULL;
|
|
word32 keySz = 0;
|
|
const byte* keyType = NULL;
|
|
word32 keyTypeSz = 0;
|
|
int ret;
|
|
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_ecdsa_bad_pad,
|
|
(word32)WSTRLEN(id_ecdsa_bad_pad), WOLFSSH_FORMAT_OPENSSH,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_KEY_FORMAT_E);
|
|
/* DoOpenSshKey never assigns *outSz, *outType, or *outTypeSz
|
|
* on the error branch (only on success),
|
|
* these assertions will catch any future regression
|
|
* where the API partially writes output before failing. */
|
|
AssertNull(key);
|
|
AssertIntEQ(keySz, 0);
|
|
AssertNull(keyType);
|
|
AssertIntEQ(keyTypeSz, 0);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_ReadKey_shortBuffer(void)
|
|
{
|
|
/* Truncated and malformed OpenSSH private key buffers that previously
|
|
* underflowed inSz inside DoOpenSshKey(), driving an out-of-bounds read in
|
|
* Base64_Decode(). Exact-size, non-terminated heap copies are used so the
|
|
* over-read is caught by AddressSanitizer if the fix ever regresses. Each
|
|
* input must be rejected with WS_PARSE_E and leave the outputs untouched. */
|
|
static const char* goodPrefix =
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
static const char* badPrefix =
|
|
"-----BEGIN NOT AN OPENSSH KEY-------\n"
|
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
const char* srcs[4];
|
|
word32 sizes[4];
|
|
byte* in;
|
|
byte* key;
|
|
word32 keySz;
|
|
const byte* keyType;
|
|
word32 keyTypeSz;
|
|
int ret;
|
|
int i;
|
|
int numCases = (int)(sizeof(srcs) / sizeof(srcs[0]));
|
|
|
|
/* begin marker only */
|
|
srcs[0] = goodPrefix;
|
|
sizes[0] = 35;
|
|
/* begin marker plus base64, no end marker, under the marker-pair length */
|
|
srcs[1] = goodPrefix;
|
|
sizes[1] = 50;
|
|
/* begin marker plus base64, no end marker, one byte under the old 70 */
|
|
srcs[2] = goodPrefix;
|
|
sizes[2] = 69;
|
|
/* wrong begin marker */
|
|
srcs[3] = badPrefix;
|
|
sizes[3] = 69;
|
|
|
|
for (i = 0; i < numCases; i++) {
|
|
in = (byte*)WMALLOC(sizes[i], NULL, DYNTYPE_FILE);
|
|
AssertNotNull(in);
|
|
WMEMCPY(in, srcs[i], sizes[i]);
|
|
|
|
key = NULL;
|
|
keySz = 0;
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer(in, sizes[i], WOLFSSH_FORMAT_OPENSSH,
|
|
&key, &keySz, &keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_PARSE_E);
|
|
AssertNull(key);
|
|
AssertIntEQ(keySz, 0);
|
|
AssertNull(keyType);
|
|
AssertIntEQ(keyTypeSz, 0);
|
|
|
|
WFREE(in, NULL, DYNTYPE_FILE);
|
|
}
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_ReadKey_noTrailingNewline(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_RSA
|
|
/* A valid OpenSSH key whose buffer ends exactly at the end marker, with no
|
|
* trailing newline, must decode to the same bytes as the canonical form.
|
|
* The old fixed "inSz - 70" arithmetic assumed a trailing newline and
|
|
* dropped the final base64 character for such inputs. */
|
|
static const char* endMarker = "-----END OPENSSH PRIVATE KEY-----";
|
|
byte* keyRef = NULL;
|
|
byte* keyTrim = NULL;
|
|
word32 keyRefSz = 0;
|
|
word32 keyTrimSz = 0;
|
|
const byte* keyType = NULL;
|
|
word32 keyTypeSz = 0;
|
|
const char* end;
|
|
word32 fullSz = (word32)WSTRLEN(id_rsa);
|
|
word32 trimSz;
|
|
int ret;
|
|
|
|
/* Canonical parse, the input ends with a trailing newline. */
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa, fullSz,
|
|
WOLFSSH_FORMAT_OPENSSH, &keyRef, &keyRefSz,
|
|
&keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(keyRef);
|
|
AssertIntGT(keyRefSz, 0);
|
|
|
|
/* Length up to and including the end marker, with the trailing newline
|
|
* dropped so the buffer ends on the last marker byte. */
|
|
end = WSTRNSTR(id_rsa, endMarker, fullSz);
|
|
AssertNotNull(end);
|
|
trimSz = (word32)(end - id_rsa) + (word32)WSTRLEN(endMarker);
|
|
AssertIntLT(trimSz, fullSz);
|
|
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa, trimSz,
|
|
WOLFSSH_FORMAT_OPENSSH, &keyTrim, &keyTrimSz,
|
|
&keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(keyTrim);
|
|
/* Must match the canonical decode exactly, not be truncated by a byte. */
|
|
AssertIntEQ(keyTrimSz, keyRefSz);
|
|
AssertIntEQ(WMEMCMP(keyTrim, keyRef, keyRefSz), 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
|
|
WFREE(keyRef, NULL, DYNTYPE_PRIVKEY);
|
|
WFREE(keyTrim, NULL, DYNTYPE_PRIVKEY);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_ReadKey_sshNoComment(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_RSA
|
|
/* An SSH format public key whose buffer ends on the last base64
|
|
* character, with no comment and no trailing newline, must decode to the
|
|
* same bytes as the canonical form. DoSshPubKey()'s old "c[inSz-1] = 0"
|
|
* wrote the null terminator over that last character. */
|
|
byte* keyRef = NULL;
|
|
byte* keyTrim = NULL;
|
|
word32 keyRefSz = 0;
|
|
word32 keyTrimSz = 0;
|
|
const byte* keyType = NULL;
|
|
word32 keyTypeSz = 0;
|
|
const char* blob;
|
|
const char* end;
|
|
word32 fullSz = (word32)WSTRLEN(id_rsa_pub);
|
|
word32 trimSz;
|
|
int ret;
|
|
|
|
/* Canonical parse, the input has a comment and a trailing newline. */
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa_pub, fullSz,
|
|
WOLFSSH_FORMAT_SSH, &keyRef, &keyRefSz,
|
|
&keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(keyRef);
|
|
AssertIntGT(keyRefSz, 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
|
|
/* Length up to the end of the base64 blob, dropping the comment. */
|
|
blob = WSTRCHR(id_rsa_pub, ' '); /* end of the "ssh-rsa" type */
|
|
AssertNotNull(blob);
|
|
end = WSTRCHR(blob + 1, ' '); /* end of the base64 blob */
|
|
AssertNotNull(end);
|
|
trimSz = (word32)(end - id_rsa_pub);
|
|
AssertIntLT(trimSz, fullSz);
|
|
|
|
keyType = NULL;
|
|
keyTypeSz = 0;
|
|
ret = wolfSSH_ReadKey_buffer((const byte*)id_rsa_pub, trimSz,
|
|
WOLFSSH_FORMAT_SSH, &keyTrim, &keyTrimSz,
|
|
&keyType, &keyTypeSz, NULL);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertNotNull(keyTrim);
|
|
/* Must match the canonical decode exactly, not be truncated by a byte. */
|
|
AssertIntEQ(keyTrimSz, keyRefSz);
|
|
AssertIntEQ(WMEMCMP(keyTrim, keyRef, keyRefSz), 0);
|
|
AssertStrEQ(keyType, "ssh-rsa");
|
|
|
|
WFREE(keyRef, NULL, DYNTYPE_PRIVKEY);
|
|
WFREE(keyTrim, NULL, DYNTYPE_PRIVKEY);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_SCP
|
|
|
|
static int my_ScpRecv(WOLFSSH* ssh, int state, const char* basePath,
|
|
const char* fileName, int fileMode, word64 mTime, word64 aTime,
|
|
word32 totalFileSz, byte* buf, word32 bufSz, word32 fileOffset,
|
|
void* ctx)
|
|
{
|
|
(void)ssh;
|
|
|
|
printf("calling scp recv cb with state %d\n", state);
|
|
printf("\tbase path = %s\n", basePath);
|
|
printf("\tfile name = %s\n", fileName);
|
|
printf("\tfile mode = %d\n", fileMode);
|
|
printf("\tfile size = %d\n", totalFileSz);
|
|
printf("\tfile offset = %d\n", fileOffset);
|
|
|
|
(void)mTime;
|
|
(void)aTime;
|
|
(void)buf;
|
|
(void)bufSz;
|
|
(void)ctx;
|
|
|
|
return WS_SCP_ABORT; /* error out for test function */
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_SCP_CB(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
int i = 3, j = 4; /* arbitrary value */
|
|
const char err[] = "test setting error msg";
|
|
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsername(NULL, NULL));
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
wolfSSH_SetScpRecv(ctx, my_ScpRecv);
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
wolfSSH_SetScpRecvCtx(ssh, (void*)&i);
|
|
AssertIntEQ(i, *(int*)wolfSSH_GetScpRecvCtx(ssh));
|
|
|
|
wolfSSH_SetScpSendCtx(ssh, (void*)&j);
|
|
AssertIntEQ(j, *(int*)wolfSSH_GetScpSendCtx(ssh));
|
|
AssertIntNE(j, *(int*)wolfSSH_GetScpRecvCtx(ssh));
|
|
|
|
AssertIntEQ(wolfSSH_SetScpErrorMsg(ssh, err), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_SetScpErrorMsg(NULL, err), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_SetScpErrorMsg(ssh, NULL), WS_BAD_ARGUMENT);
|
|
|
|
AssertIntEQ(wolfSSH_SCP_accept(NULL), WS_BAD_ARGUMENT);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
#else /* WOLFSSH_SCP */
|
|
static void test_wolfSSH_SCP_CB(void) { ; }
|
|
#endif /* WOLFSSH_SCP */
|
|
|
|
#if defined(WOLFSSH_SCP) && defined(WOLFSSH_HAVE_SYMLINK) && \
|
|
!defined(WOLFSSH_SCP_USER_CALLBACKS) && !defined(SINGLE_THREADED) && \
|
|
!defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API) && \
|
|
!defined(NO_WOLFSSH_DIR)
|
|
/* The default SCP send callback must refuse a symlink so its target is not
|
|
* streamed to the peer. The example client cannot issue "scp -f -r", so the
|
|
* recursive guards are unreachable through scripts/scp.test; drive both the
|
|
* recursive-root and per-entry guards here at the callback level, and exercise
|
|
* wFopenNoFollow (shared by the file opens) directly. */
|
|
static void test_wolfSSH_SCP_SendSymlinkReject(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
ScpSendCtx sendCtx;
|
|
WFILE* fp = NULL;
|
|
WDIR wdir;
|
|
char scpRoot[] = "/tmp/wolfssh_scp_sym_XXXXXX";
|
|
char realFile[WOLFSSH_MAX_FILENAME];
|
|
char symToFile[WOLFSSH_MAX_FILENAME];
|
|
char symToDir[WOLFSSH_MAX_FILENAME];
|
|
char symToDirSlash[WOLFSSH_MAX_FILENAME];
|
|
char subDir[WOLFSSH_MAX_FILENAME];
|
|
char subLink[WOLFSSH_MAX_FILENAME];
|
|
char fileName[DEFAULT_SCP_FILE_NAME_SZ];
|
|
byte buf[256];
|
|
word64 mTime = 0;
|
|
word64 aTime = 0;
|
|
int fileMode = 0;
|
|
word32 totalSz = 0;
|
|
|
|
AssertNotNull(mkdtemp(scpRoot));
|
|
|
|
WSNPRINTF(realFile, sizeof(realFile), "%s/secret", scpRoot);
|
|
WSNPRINTF(symToFile, sizeof(symToFile), "%s/link_file", scpRoot);
|
|
WSNPRINTF(symToDir, sizeof(symToDir), "%s/link_dir", scpRoot);
|
|
|
|
/* stage an empty real file plus a symlink to it and to the temp dir */
|
|
AssertIntEQ(WFOPEN(NULL, &fp, realFile, "wb"), 0);
|
|
WFCLOSE(NULL, fp);
|
|
fp = NULL;
|
|
AssertIntEQ(symlink(realFile, symToFile), 0);
|
|
AssertIntEQ(symlink(scpRoot, symToDir), 0);
|
|
|
|
/* the guard's discriminator: a real path is not a link, the planted ones
|
|
* are - so a genuine directory would not trip the recursive-root check */
|
|
AssertIntEQ(wIsSymlink(scpRoot), 0);
|
|
AssertIntEQ(wIsSymlink(symToDir), 1);
|
|
|
|
/* wFopenNoFollow opens a real file but refuses a symlink */
|
|
AssertIntEQ(wFopenNoFollow(NULL, &fp, realFile), 0);
|
|
AssertNotNull(fp);
|
|
WFCLOSE(NULL, fp);
|
|
fp = NULL;
|
|
AssertIntNE(wFopenNoFollow(NULL, &fp, symToFile), 0);
|
|
AssertNull(fp);
|
|
|
|
/* wOpendirNoFollow opens a real directory but refuses a symlinked one */
|
|
AssertIntEQ(wOpendirNoFollow(NULL, &wdir, scpRoot), 0);
|
|
WCLOSEDIR(NULL, &wdir);
|
|
AssertIntNE(wOpendirNoFollow(NULL, &wdir, symToDir), 0);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
WMEMSET(&sendCtx, 0, sizeof(sendCtx));
|
|
WMEMSET(fileName, 0, sizeof(fileName));
|
|
|
|
/* a benign state returns success (the callback does not blanket-abort) */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_NEW_REQUEST, symToDir,
|
|
fileName, (word32)sizeof(fileName), &mTime, &aTime, &fileMode, 0,
|
|
&totalSz, buf, (word32)sizeof(buf), &sendCtx), WS_SUCCESS);
|
|
|
|
/* a single-file request for a symlink is refused (WS_BAD_FILE_E) */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_SINGLE_FILE_REQUEST,
|
|
symToFile, fileName, (word32)sizeof(fileName), &mTime, &aTime,
|
|
&fileMode, 0, &totalSz, buf, (word32)sizeof(buf), &sendCtx),
|
|
WS_BAD_FILE_E);
|
|
AssertNull(sendCtx.fp);
|
|
|
|
/* a recursive root that is a symlink aborts before any directory is
|
|
* opened, so nothing is pushed onto the stack and nothing leaks */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST, symToDir,
|
|
fileName, (word32)sizeof(fileName), &mTime, &aTime, &fileMode, 0,
|
|
&totalSz, buf, (word32)sizeof(buf), &sendCtx), WS_SCP_ABORT);
|
|
AssertNull(sendCtx.currentDir);
|
|
|
|
/* a trailing separator must not bypass the root guard: lstat on "link/"
|
|
* would follow the link, so the guard strips it before checking */
|
|
WSNPRINTF(symToDirSlash, sizeof(symToDirSlash), "%s/link_dir/", scpRoot);
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST,
|
|
symToDirSlash, fileName, (word32)sizeof(fileName), &mTime, &aTime,
|
|
&fileMode, 0, &totalSz, buf, (word32)sizeof(buf), &sendCtx),
|
|
WS_SCP_ABORT);
|
|
AssertNull(sendCtx.currentDir);
|
|
|
|
/* a recursive root with a missing path must abort, not dereference NULL */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST, NULL,
|
|
fileName, (word32)sizeof(fileName), &mTime, &aTime, &fileMode, 0,
|
|
&totalSz, buf, (word32)sizeof(buf), &sendCtx), WS_SCP_ABORT);
|
|
AssertNull(sendCtx.currentDir);
|
|
|
|
/* per-entry guard: drive two iterations so ScpProcessEntry processes a
|
|
* planted entry. The link points at a directory on purpose - a symlinked
|
|
* dir entry is caught only by the per-entry wIsSymlink check (not the
|
|
* file-open no-follow guard), so this isolates that branch. */
|
|
WSNPRINTF(subDir, sizeof(subDir), "%s/sub", scpRoot);
|
|
WSNPRINTF(subLink, sizeof(subLink), "%s/sub/evil", scpRoot);
|
|
AssertIntEQ(WMKDIR(NULL, subDir, 0700), 0);
|
|
AssertIntEQ(symlink(scpRoot, subLink), 0);
|
|
|
|
WMEMSET(&sendCtx, 0, sizeof(sendCtx));
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST, subDir,
|
|
fileName, (word32)sizeof(fileName), &mTime, &aTime, &fileMode, 0,
|
|
&totalSz, buf, (word32)sizeof(buf), &sendCtx), WS_SCP_ENTER_DIR);
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST, subDir,
|
|
fileName, (word32)sizeof(fileName), &mTime, &aTime, &fileMode, 0,
|
|
&totalSz, buf, (word32)sizeof(buf), &sendCtx), WS_SCP_ABORT);
|
|
/* the per-entry abort left the pushed dir on the stack (this is what would
|
|
* leak); the production teardown drain must release it */
|
|
AssertNotNull(sendCtx.currentDir);
|
|
ScpSendCtxFreeDirs(ssh->fs, &sendCtx, ssh->ctx->heap);
|
|
AssertNull(sendCtx.currentDir);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
WREMOVE(NULL, subLink);
|
|
WRMDIR(NULL, subDir);
|
|
WREMOVE(NULL, symToFile);
|
|
WREMOVE(NULL, symToDir);
|
|
WREMOVE(NULL, realFile);
|
|
WRMDIR(NULL, scpRoot);
|
|
}
|
|
#else
|
|
static void test_wolfSSH_SCP_SendSymlinkReject(void) { ; }
|
|
#endif
|
|
|
|
#if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \
|
|
!defined(NO_FILESYSTEM) && !defined(NO_WOLFSSH_DIR)
|
|
|
|
static int scpStageRecurFile(const char* name, const byte* buf, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
int ret = 0;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "wb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFWRITE(NULL, buf, 1, sz, fp) != sz)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
/* A recursive send must hand back the directory's entries instead of going
|
|
* straight to the final exit. The example client cannot issue "scp -r -f",
|
|
* so drive the walk through wsScpSendCallback() at the callback level. */
|
|
static void test_wolfSSH_SCP_SendRecursiveEntry(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
ScpSendCtx sendCtx;
|
|
char fileName[DEFAULT_SCP_FILE_NAME_SZ];
|
|
char filePath[DEFAULT_SCP_FILE_NAME_SZ];
|
|
char entryName[] = "a.txt";
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
char dirPath[] = CONFIG_WOLFSSH_SFTP_DEFAULT_DIR "/scp_recur_entry";
|
|
#else
|
|
char dirPath[] = "./scp_recur_entry";
|
|
#endif
|
|
byte data[64];
|
|
byte buf[256];
|
|
word64 mTime = 0;
|
|
word64 aTime = 0;
|
|
word32 totalSz = 0;
|
|
word32 i;
|
|
int fileMode = 0;
|
|
|
|
for (i = 0; i < (word32)sizeof(data); i++)
|
|
data[i] = (byte)((i * 7 + 3) & 0xff);
|
|
|
|
WSNPRINTF(filePath, sizeof(filePath), "%s/%s", dirPath, entryName);
|
|
|
|
/* full teardown first: a run that aborted mid-test leaves these behind,
|
|
* and then WMKDIR below fails with EEXIST, masking the real failure with
|
|
* a setup error */
|
|
WREMOVE(NULL, filePath);
|
|
WRMDIR(NULL, dirPath);
|
|
|
|
AssertIntEQ(WMKDIR(NULL, dirPath, 0700), 0);
|
|
AssertIntEQ(scpStageRecurFile(filePath, data, sizeof(data)), 0);
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
WMEMSET(&sendCtx, 0, sizeof(sendCtx));
|
|
WMEMSET(fileName, 0, sizeof(fileName));
|
|
|
|
/* the first call opens the root and reports the directory itself */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST,
|
|
dirPath, fileName, (word32)sizeof(fileName), &mTime, &aTime,
|
|
&fileMode, 0, &totalSz, buf, (word32)sizeof(buf), &sendCtx),
|
|
WS_SCP_ENTER_DIR);
|
|
|
|
/* the second call must produce the staged entry, skipping "." and ".." to
|
|
* reach it; the return is the number of file bytes placed in buf */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST,
|
|
dirPath, fileName, (word32)sizeof(fileName), &mTime, &aTime,
|
|
&fileMode, 0, &totalSz, buf, (word32)sizeof(buf), &sendCtx),
|
|
(int)sizeof(data));
|
|
AssertStrEQ(fileName, entryName);
|
|
AssertIntEQ(totalSz, (word32)sizeof(data));
|
|
AssertIntEQ(XMEMCMP(buf, data, sizeof(data)), 0);
|
|
|
|
/* the third call exhausts the directory and pops the only stack entry */
|
|
AssertIntEQ(wsScpSendCallback(ssh, WOLFSSH_SCP_RECURSIVE_REQUEST,
|
|
dirPath, fileName, (word32)sizeof(fileName), &mTime, &aTime,
|
|
&fileMode, 0, &totalSz, buf, (word32)sizeof(buf), &sendCtx),
|
|
WS_SCP_EXIT_DIR_FINAL);
|
|
AssertNull(sendCtx.currentDir);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
WREMOVE(NULL, filePath);
|
|
WRMDIR(NULL, dirPath);
|
|
}
|
|
#else
|
|
static void test_wolfSSH_SCP_SendRecursiveEntry(void) { ; }
|
|
#endif
|
|
|
|
#ifdef WOLFSSH_AGENT
|
|
/* Room for an add-identity message carrying a 3072-bit RSA key. */
|
|
#define AGENT_TEST_BUF_SZ 2048
|
|
|
|
typedef struct AgentTestCtx {
|
|
int partialWrite;
|
|
byte response[AGENT_TEST_BUF_SZ];
|
|
word32 responseSz;
|
|
int writeCalls;
|
|
int readCalls;
|
|
} AgentTestCtx;
|
|
|
|
static int test_agent_cb(WS_AgentCbAction action, void* ctx)
|
|
{
|
|
(void)ctx;
|
|
|
|
if (action == WOLFSSH_AGENT_LOCAL_SETUP ||
|
|
action == WOLFSSH_AGENT_LOCAL_CLEANUP) {
|
|
return WS_AGENT_SUCCESS;
|
|
}
|
|
|
|
return WS_AGENT_INVALID_ACTION;
|
|
}
|
|
|
|
static void put_uint32(byte* dst, word32 value)
|
|
{
|
|
dst[0] = (byte)((value >> 24) & 0xff);
|
|
dst[1] = (byte)((value >> 16) & 0xff);
|
|
dst[2] = (byte)((value >> 8) & 0xff);
|
|
dst[3] = (byte)(value & 0xff);
|
|
}
|
|
|
|
static void build_agent_message(byte* out, word32* outSz, byte id,
|
|
const byte* body, word32 bodySz)
|
|
{
|
|
word32 payloadSz = 1 + bodySz;
|
|
|
|
put_uint32(out, payloadSz);
|
|
out[4] = id;
|
|
if (bodySz > 0)
|
|
memcpy(out + 5, body, bodySz);
|
|
*outSz = payloadSz + LENGTH_SZ;
|
|
}
|
|
|
|
static void build_sign_response(AgentTestCtx* ctx, const byte* sig,
|
|
word32 sigSz)
|
|
{
|
|
byte body[AGENT_TEST_BUF_SZ];
|
|
|
|
AssertTrue(LENGTH_SZ + sigSz <= sizeof(body));
|
|
AssertTrue(LENGTH_SZ + sigSz + LENGTH_SZ + MSG_ID_SZ
|
|
<= sizeof(ctx->response));
|
|
put_uint32(body, sigSz);
|
|
if (sigSz > 0)
|
|
memcpy(body + LENGTH_SZ, sig, sigSz);
|
|
build_agent_message(ctx->response, &ctx->responseSz,
|
|
MSGID_AGENT_SIGN_RESPONSE, body, LENGTH_SZ + sigSz);
|
|
}
|
|
|
|
static void build_simple_response(AgentTestCtx* ctx, byte id)
|
|
{
|
|
build_agent_message(ctx->response, &ctx->responseSz, id, NULL, 0);
|
|
}
|
|
|
|
static int test_agent_io_cb(WS_AgentIoCbAction action, void* buf, word32 bufSz,
|
|
void* ctx)
|
|
{
|
|
AgentTestCtx* io = (AgentTestCtx*)ctx;
|
|
|
|
if (action == WOLFSSH_AGENT_IO_WRITE) {
|
|
io->writeCalls++;
|
|
if (io->partialWrite && bufSz > 0) {
|
|
io->partialWrite = 0;
|
|
return (int)(bufSz - 1);
|
|
}
|
|
return (int)bufSz;
|
|
}
|
|
|
|
io->readCalls++;
|
|
if (io->responseSz == 0 || bufSz < io->responseSz)
|
|
return 0;
|
|
memcpy(buf, io->response, io->responseSz);
|
|
return (int)io->responseSz;
|
|
}
|
|
|
|
static void setup_agent_test(WOLFSSH_CTX** ctx, WOLFSSH** ssh, AgentTestCtx* io)
|
|
{
|
|
AssertNotNull(*ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertIntEQ(wolfSSH_CTX_AGENT_enable(*ctx, 1), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_set_agent_cb(*ctx, test_agent_cb,
|
|
test_agent_io_cb), WS_SUCCESS);
|
|
AssertNotNull(*ssh = wolfSSH_new(*ctx));
|
|
AssertNotNull((*ssh)->agent = wolfSSH_AGENT_new((*ctx)->heap));
|
|
AssertIntEQ(wolfSSH_set_agent_cb_ctx(*ssh, io), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_AGENT_enable(*ssh, 1), WS_SUCCESS);
|
|
}
|
|
|
|
static void cleanup_agent_test(WOLFSSH_CTX* ctx, WOLFSSH* ssh)
|
|
{
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
#if !defined(WOLFSSH_NO_RSA_SHA2_256) || \
|
|
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521)
|
|
/* Writes an SSH string. */
|
|
static word32 build_string(byte* out, const byte* val, word32 valSz)
|
|
{
|
|
put_uint32(out, valSz);
|
|
if (valSz > 0)
|
|
memcpy(out + LENGTH_SZ, val, valSz);
|
|
|
|
return LENGTH_SZ + valSz;
|
|
}
|
|
|
|
/* Writes an mpint, padding with a zero byte when the sign bit is set.
|
|
* DoAddIdentity() parses the key values with GetMpint(). */
|
|
static word32 build_mpint(byte* out, const byte* val, word32 valSz)
|
|
{
|
|
word32 idx = LENGTH_SZ;
|
|
byte pad = (valSz > 0 && (val[0] & 0x80)) ? 1 : 0;
|
|
|
|
put_uint32(out, valSz + pad);
|
|
if (pad)
|
|
out[idx++] = 0;
|
|
if (valSz > 0)
|
|
memcpy(out + idx, val, valSz);
|
|
|
|
return idx + valSz;
|
|
}
|
|
|
|
#endif
|
|
|
|
static void test_wolfSSH_agent_signrequest_partial_write(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[8];
|
|
word32 sigSz = sizeof(sig);
|
|
int ret;
|
|
|
|
memset(&io, 0, sizeof(io));
|
|
io.partialWrite = 1;
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_CXN_FAIL);
|
|
AssertIntEQ(sigSz, 0);
|
|
AssertIntEQ(io.writeCalls, 1);
|
|
AssertIntEQ(io.readCalls, 0);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
static void test_wolfSSH_agent_signrequest_wrong_message(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[16];
|
|
word32 sigSz = sizeof(sig);
|
|
int ret;
|
|
|
|
memset(&io, 0, sizeof(io));
|
|
build_simple_response(&io, MSGID_AGENT_SUCCESS);
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
AssertIntEQ(sigSz, 0);
|
|
AssertIntEQ(io.writeCalls, 1);
|
|
AssertIntEQ(io.readCalls, 1);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
static void test_wolfSSH_agent_signrequest_signature_too_large(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte signatureData[12];
|
|
byte sig[8];
|
|
word32 sigSz = sizeof(sig);
|
|
int ret;
|
|
|
|
memset(signatureData, 0x5a, sizeof(signatureData));
|
|
memset(&io, 0, sizeof(io));
|
|
build_sign_response(&io, signatureData, sizeof(signatureData));
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_BUFFER_E);
|
|
AssertIntEQ(sigSz, 0);
|
|
AssertIntEQ(io.writeCalls, 1);
|
|
AssertIntEQ(io.readCalls, 1);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
/* An RSA-4096 signature makes a 521 byte reply, more than the agent
|
|
* read buffer held before it was sized from WOLFSSH_AGENT_MAX_RSP_SZ. */
|
|
static void test_wolfSSH_agent_signrequest_large_response(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte signatureData[512];
|
|
byte sig[1024];
|
|
word32 sigSz = sizeof(sig);
|
|
int ret;
|
|
|
|
memset(signatureData, 0xa5, sizeof(signatureData));
|
|
memset(&io, 0, sizeof(io));
|
|
build_sign_response(&io, signatureData, sizeof(signatureData));
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertIntEQ(sigSz, sizeof(signatureData));
|
|
AssertTrue(memcmp(sig, signatureData, sizeof(signatureData)) == 0);
|
|
AssertIntEQ(io.readCalls, 1);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
static void test_wolfSSH_agent_signrequest_success(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte signatureData[8];
|
|
byte sig[16];
|
|
word32 sigSz = sizeof(sig);
|
|
int ret;
|
|
|
|
memset(signatureData, 0xa5, sizeof(signatureData));
|
|
memset(&io, 0, sizeof(io));
|
|
build_sign_response(&io, signatureData, sizeof(signatureData));
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertIntEQ(sigSz, sizeof(signatureData));
|
|
AssertTrue(memcmp(sig, signatureData, sizeof(signatureData)) == 0);
|
|
AssertIntEQ(io.writeCalls, 1);
|
|
AssertIntEQ(io.readCalls, 1);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
|
/* A hostile agent can answer a sign request with its own messages. This
|
|
* identity carries no private values, so signing fails and the error
|
|
* reaches the caller. */
|
|
static void test_wolfSSH_agent_signrequest_oversize_rsa_key(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte modulus[384];
|
|
byte body[512];
|
|
byte data[16];
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[16];
|
|
word32 sigSz = sizeof(sig);
|
|
word32 idx, i;
|
|
int ret;
|
|
|
|
/* 3072-bit modulus. The leading byte must have the sign bit clear or
|
|
* GetMpint() rejects the value as non-canonical. */
|
|
memset(modulus, 0xa5, sizeof(modulus));
|
|
modulus[0] = 0x01;
|
|
memset(data, 0x5a, sizeof(data));
|
|
memset(&io, 0, sizeof(io));
|
|
|
|
/* MSGID_AGENT_ADD_IDENTITY: ssh-rsa, n, e, then empty d/iqmp/p/q and
|
|
* comment. The private values are never reached. */
|
|
idx = 0;
|
|
put_uint32(body + idx, 7);
|
|
idx += LENGTH_SZ;
|
|
memcpy(body + idx, "ssh-rsa", 7);
|
|
idx += 7;
|
|
put_uint32(body + idx, sizeof(modulus));
|
|
idx += LENGTH_SZ;
|
|
memcpy(body + idx, modulus, sizeof(modulus));
|
|
idx += sizeof(modulus);
|
|
put_uint32(body + idx, 3);
|
|
idx += LENGTH_SZ;
|
|
body[idx++] = 0x01;
|
|
body[idx++] = 0x00;
|
|
body[idx++] = 0x01;
|
|
for (i = 0; i < 5; i++) {
|
|
put_uint32(body + idx, 0);
|
|
idx += LENGTH_SZ;
|
|
}
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_ADD_IDENTITY, body, idx);
|
|
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
/* The agent answers with an add-identity instead of a signature. The
|
|
* identity is stored, but the caller is told there was no key. */
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
AssertNotNull(ssh->agent->idList);
|
|
|
|
/* MSGID_AGENT_SIGN_REQUEST for that identity. The key blob is the
|
|
* n and e pair, matched by its SHA-256 digest. */
|
|
idx = 0;
|
|
put_uint32(body + idx, (LENGTH_SZ * 2) + sizeof(modulus) + 3);
|
|
idx += LENGTH_SZ;
|
|
put_uint32(body + idx, sizeof(modulus));
|
|
idx += LENGTH_SZ;
|
|
memcpy(body + idx, modulus, sizeof(modulus));
|
|
idx += sizeof(modulus);
|
|
put_uint32(body + idx, 3);
|
|
idx += LENGTH_SZ;
|
|
body[idx++] = 0x01;
|
|
body[idx++] = 0x00;
|
|
body[idx++] = 0x01;
|
|
put_uint32(body + idx, sizeof(data));
|
|
idx += LENGTH_SZ;
|
|
memcpy(body + idx, data, sizeof(data));
|
|
idx += sizeof(data);
|
|
put_uint32(body + idx, AGENT_SIGN_RSA_SHA2_256);
|
|
idx += LENGTH_SZ;
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_SIGN_REQUEST, body, idx);
|
|
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_RSA_E);
|
|
AssertIntEQ(sigSz, 0);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
}
|
|
|
|
/* Components of a 3072-bit RSA key, in the order an add-identity message
|
|
* carries them. */
|
|
static const char agentRsa3072N[] =
|
|
"d6232af3e38ffa6a3b46be8585b7d1284957c0bb813c19203cfd03e1bd8ae4f5"
|
|
"ef5ea4c11d49dd102f8f379d36c8e92d921f69958d207d73a36c4f1a06c9db80"
|
|
"7048c257a2adba37deac8c268120aafe81a98bcdd56399c979296d590b873baf"
|
|
"27cd5b35e7b4cb8aa1db862451c195f123e049a02f0a696ad83ff9b1e91b9b1a"
|
|
"8d355f8a085eb946d31456e47b016d5e26abeac2addee4076a984ecb31c9f005"
|
|
"8d2e056c9477a5c2a464bdba8e1303d58bf3d92f4be1713518f56fb39f496ea2"
|
|
"543782efb974b9ed9766c1a5be94f91ff3839a5c34e0d2a0958d99114ccf3241"
|
|
"2d8c236f545052e707e9fe2984334b75696eb830590e4a669211ca39c22df48a"
|
|
"6b5e35a83f95c323342d26bd9e465e5362732c1361ecb34b52761906daf01c1e"
|
|
"79773f40a7e59afdd7cab0bbcc8969e130c76f6bad18a8b5f7a989b538ef1520"
|
|
"c9db10c2ea3ffd5af49dc0529b342a8387f006684176621619fcca6994ee10ca"
|
|
"66ef37cb73103f6c91117e052404be4d55703c6f74efb61df9da5f2ff103501d";
|
|
static const char agentRsa3072E[] =
|
|
"010001";
|
|
static const char agentRsa3072D[] =
|
|
"03055d41dcf8194110580496aae7e67730aa7e6d8b076edaa513c0d7c4237bca"
|
|
"6da01c27a5f629c50f40d6c15100fa2308589a3c6c94e6cad85889dae0a45f84"
|
|
"68fbc7ed0971a87462b963501c6a71de0c92700f0b29214195e1e6b3b7dbd12e"
|
|
"5ef99c854dba9fba7bfccfcf385c73173ee87da059310282fb225ba6deebda25"
|
|
"7c3f3c25fade72f7b1a7a35e94f0ff3f1827b467b85cd442a6a86d14f1c5e7d7"
|
|
"a768433c53be68c61506e8022eb7cc4a2640cd318c0e11b75b266aff91b09c04"
|
|
"4bd66b5faf0e962104f25315985f9e15d5b8866efe6d87f4e9fe9d98b0c8bfb6"
|
|
"783040232f7d256a96ce15ce5f392dedb2b2d8a3318cec105078ef71aece3920"
|
|
"ef63d8aa2e607c1534cfabd1fb9e0962197cd60d93d12edde5d4623dbf9cc4bd"
|
|
"6fbd9b0695a51f3df3c8a8c4a4615568a594a08695a663c2234982ead30a4f91"
|
|
"a858705027159580b28823053b17cadadbba0a198beff54fc14eac76476e4594"
|
|
"0ff3ae2af0646a162ffa1ee5128af12909aaba7e16f4f55feedce061f4de3771";
|
|
static const char agentRsa3072Iqmp[] =
|
|
"b2cbe2bd4d43bdb1ec9119abe5b3f408e193501e939460db0de6991016c76cb4"
|
|
"9b8f293db38cdb9842ef22ad0b64c41bbe583e20bed68a1923be1911553b47b4"
|
|
"ca7c3d9be5504aa37127ee8cfbd9b705290d3426bdbe8c2dbbfe5a9db1595c9e"
|
|
"db4d5b2d4acb028ff25af5625a20a2e2a57eba47090bfbbd87a28c33a4e55004"
|
|
"bbdcb5db57d5c207dde0223f37adf1f6649b716c94aadbac99026eab591df81c"
|
|
"cef23efadb120b2a3709eac248106303385fa7284032b4bf68b34325e43980f8";
|
|
static const char agentRsa3072P[] =
|
|
"f348e42165c7545c90d12214c409af2774566c1b58ed123758f7622dd3fcb069"
|
|
"def6a12d963719f5f119af55234cd98a6aad12f09c0edcf5a53cd3a751304c6c"
|
|
"7d69d98c62ad52a6f78ce54670449d34f888ab61b16b01bdfad3a9bffa5c49ab"
|
|
"6dbded21d31db0811785bca44ac720681baf811b289461a682dcf4555df3ec8d"
|
|
"4776f6991b2a2ddb6ac06b80bd2be87041fa394bd3c14c392b65aef3a6d8c005"
|
|
"83c8108d0cc38ca708c20fd5635dd0b16a5f0344ef88e227552195a7ea7f5395";
|
|
static const char agentRsa3072Q[] =
|
|
"e1544a375c1f125ea8590ce99ccbc986ea906b91b880f7cdb6bb4d1c9241da72"
|
|
"e857bf296f1359da9283e712dab9a8788830cbd3a2ca6d70b3ed5c8db3bcbebb"
|
|
"4c20f5d41e8c7a641f24d1fc3daeb4ecff60f187e34c6cd0d46c5217e4abaf35"
|
|
"a7fcab3c05eacea87937932034c8300f6a8e9f052efe5d1ee01daeac0528dc54"
|
|
"9666d61dc17f95b2dd1d6827ca6c196eca129254f7bbe21e53cd8167a766067f"
|
|
"aaa349097851c0b2d0d575e2c9425cb5f27f47484373d2dbb5e07d49401fe869";
|
|
|
|
/* A 3072-bit identity needs a 384 byte signature buffer, more than the
|
|
* 2048-bit keys the agent used to assume. */
|
|
static void test_wolfSSH_agent_signrequest_rsa_3072(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte body[AGENT_TEST_BUF_SZ];
|
|
byte data[16];
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[16];
|
|
byte* n = NULL;
|
|
byte* e = NULL;
|
|
byte* d = NULL;
|
|
byte* iqmp = NULL;
|
|
byte* p = NULL;
|
|
byte* q = NULL;
|
|
word32 nSz = 0, eSz = 0, dSz = 0, iqmpSz = 0, pSz = 0, qSz = 0;
|
|
word32 sigSz = sizeof(sig);
|
|
word32 idx, blobSz;
|
|
int ret;
|
|
|
|
AssertIntEQ(0, ConvertHexToBin(agentRsa3072N, &n, &nSz,
|
|
agentRsa3072E, &e, &eSz, agentRsa3072D, &d, &dSz,
|
|
agentRsa3072Iqmp, &iqmp, &iqmpSz));
|
|
AssertIntEQ(0, ConvertHexToBin(agentRsa3072P, &p, &pSz,
|
|
agentRsa3072Q, &q, &qSz, NULL, NULL, NULL, NULL, NULL, NULL));
|
|
|
|
memset(data, 0x5a, sizeof(data));
|
|
memset(&io, 0, sizeof(io));
|
|
|
|
idx = 0;
|
|
idx += build_string(body + idx, (const byte*)"ssh-rsa", 7);
|
|
idx += build_mpint(body + idx, n, nSz);
|
|
idx += build_mpint(body + idx, e, eSz);
|
|
idx += build_mpint(body + idx, d, dSz);
|
|
idx += build_mpint(body + idx, iqmp, iqmpSz);
|
|
idx += build_mpint(body + idx, p, pSz);
|
|
idx += build_mpint(body + idx, q, qSz);
|
|
idx += build_string(body + idx, (const byte*)"", 0);
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_ADD_IDENTITY, body, idx);
|
|
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
/* The agent answers with an add-identity instead of a signature. The
|
|
* identity is stored, but the caller is told there was no key. */
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
AssertNotNull(ssh->agent->idList);
|
|
|
|
/* Sign request for that identity. The key blob is the n and e pair,
|
|
* matched by its SHA-256 digest. */
|
|
idx = LENGTH_SZ;
|
|
blobSz = build_mpint(body + idx, n, nSz);
|
|
blobSz += build_mpint(body + idx + blobSz, e, eSz);
|
|
put_uint32(body, blobSz);
|
|
idx += blobSz;
|
|
idx += build_string(body + idx, data, sizeof(data));
|
|
put_uint32(body + idx, AGENT_SIGN_RSA_SHA2_256);
|
|
idx += LENGTH_SZ;
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_SIGN_REQUEST, body, idx);
|
|
|
|
/* Signing runs to completion, and SignHashRsa() verifies its own
|
|
* result. The reply was a request, so the caller still sees no key;
|
|
* a failed signature would surface as WS_RSA_E instead. */
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
|
|
/* Clearing the private exponent proves the request above matched this
|
|
* identity and signed. A request that never matched would still
|
|
* report no key. */
|
|
memset(ssh->agent->idList->key.rsa.d, 0,
|
|
ssh->agent->idList->key.rsa.dSz);
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_RSA_E);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
FreeBins(n, e, d, iqmp);
|
|
FreeBins(p, q, NULL, NULL);
|
|
}
|
|
#endif /* WOLFSSH_NO_RSA_SHA2_256 */
|
|
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
|
|
/* A P-521 key produces the largest signature the ECDSA path re-encodes,
|
|
* so it is the tightest fit for the agent's signature buffer. */
|
|
static const char agentEccP521Q[] =
|
|
"04005db4ee766c15652f9383c97e7054251d70d4c3691da61fb5b8905eba479d"
|
|
"28546f9d92b73ece380811e1c157a6e9402e0e9e1556b43884309b4089c4b303"
|
|
"b37cec0082eec861ba370fe930b48989aa7f8f96b9f226abe85916b6f452cb02"
|
|
"6f3f50c87a663b9fe43c143aecbcf9d42a042ef942cf672071f01170c5b66edd"
|
|
"b5abbfd4e0";
|
|
|
|
static const char agentEccP521D[] =
|
|
"00a7d17630ef3ee4343ea7427745e32fa219843ba6a99f689c2948da2f3d53cc"
|
|
"0ae0a92d551b6f993729acada20fb52eeadfa53ac4669a3364a25947b594f085"
|
|
"50ae";
|
|
|
|
static void test_wolfSSH_agent_signrequest_ecc_p521(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte body[AGENT_TEST_BUF_SZ];
|
|
byte data[16];
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[16];
|
|
byte* q = NULL;
|
|
byte* d = NULL;
|
|
word32 qSz = 0, dSz = 0;
|
|
word32 sigSz = sizeof(sig);
|
|
word32 idx, blobSz;
|
|
int ret;
|
|
|
|
AssertIntEQ(0, ConvertHexToBin(agentEccP521Q, &q, &qSz,
|
|
agentEccP521D, &d, &dSz, NULL, NULL, NULL, NULL, NULL, NULL));
|
|
|
|
memset(data, 0x5a, sizeof(data));
|
|
memset(&io, 0, sizeof(io));
|
|
|
|
/* Add identity: key type, curve name, Q, d, then the comment. */
|
|
idx = 0;
|
|
idx += build_string(body + idx, (const byte*)"ecdsa-sha2-nistp521", 19);
|
|
idx += build_string(body + idx, (const byte*)"nistp521", 8);
|
|
idx += build_mpint(body + idx, q, qSz);
|
|
idx += build_mpint(body + idx, d, dSz);
|
|
idx += build_string(body + idx, (const byte*)"", 0);
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_ADD_IDENTITY, body, idx);
|
|
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
AssertNotNull(ssh->agent->idList);
|
|
|
|
/* Sign request for that identity. The key blob is the curve name and
|
|
* Q pair, matched by its SHA-256 digest. */
|
|
idx = LENGTH_SZ;
|
|
blobSz = build_string(body + idx, (const byte*)"nistp521", 8);
|
|
blobSz += build_mpint(body + idx + blobSz, q, qSz);
|
|
put_uint32(body, blobSz);
|
|
idx += blobSz;
|
|
idx += build_string(body + idx, data, sizeof(data));
|
|
put_uint32(body + idx, 0);
|
|
idx += LENGTH_SZ;
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_SIGN_REQUEST, body, idx);
|
|
|
|
/* Signing and the r and s re-encode both run to completion. A buffer
|
|
* too small for either would surface as WS_ECC_E. */
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
|
|
/* Corrupting the stored point proves the request above matched this
|
|
* identity and signed. A request that never matched would still
|
|
* report no key. The digest match keeps FindKeyId() finding it. */
|
|
ssh->agent->idList->key.ecdsa.q[0] = 0;
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_ECC_E);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
FreeBins(q, d, NULL, NULL);
|
|
}
|
|
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP521 */
|
|
|
|
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
|
/* Adds an RSA identity with the given modulus through the fake agent,
|
|
* then signs with it. Returns the result of the sign request. */
|
|
static int agent_rsa_sign_result(const byte* modulus, word32 modulusSz)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
AgentTestCtx io;
|
|
byte body[AGENT_TEST_BUF_SZ];
|
|
byte e[3];
|
|
byte data[16];
|
|
byte digest[16] = {0};
|
|
byte keyBlob[8] = {0};
|
|
byte sig[16];
|
|
word32 sigSz = sizeof(sig);
|
|
word32 idx, blobSz, i;
|
|
int ret;
|
|
|
|
e[0] = 0x01;
|
|
e[1] = 0x00;
|
|
e[2] = 0x01;
|
|
memset(data, 0x5a, sizeof(data));
|
|
memset(&io, 0, sizeof(io));
|
|
|
|
/* Add identity: the private values are never reached. */
|
|
idx = 0;
|
|
idx += build_string(body + idx, (const byte*)"ssh-rsa", 7);
|
|
idx += build_mpint(body + idx, modulus, modulusSz);
|
|
idx += build_mpint(body + idx, e, sizeof(e));
|
|
for (i = 0; i < 4; i++)
|
|
idx += build_mpint(body + idx, NULL, 0);
|
|
idx += build_string(body + idx, (const byte*)"", 0);
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_ADD_IDENTITY, body, idx);
|
|
|
|
setup_agent_test(&ctx, &ssh, &io);
|
|
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
AssertIntEQ(ret, WS_AGENT_NO_KEY_E);
|
|
AssertNotNull(ssh->agent->idList);
|
|
|
|
/* Sign request for that identity, matched on the n and e pair. */
|
|
idx = LENGTH_SZ;
|
|
blobSz = build_mpint(body + idx, modulus, modulusSz);
|
|
blobSz += build_mpint(body + idx + blobSz, e, sizeof(e));
|
|
put_uint32(body, blobSz);
|
|
idx += blobSz;
|
|
idx += build_string(body + idx, data, sizeof(data));
|
|
put_uint32(body + idx, AGENT_SIGN_RSA_SHA2_256);
|
|
idx += LENGTH_SZ;
|
|
AssertTrue(idx <= sizeof(body));
|
|
AssertTrue(idx + LENGTH_SZ + MSG_ID_SZ <= sizeof(io.response));
|
|
build_agent_message(io.response, &io.responseSz,
|
|
MSGID_AGENT_SIGN_REQUEST, body, idx);
|
|
|
|
sigSz = sizeof(sig);
|
|
ret = wolfSSH_AGENT_SignRequest(ssh, digest, sizeof(digest),
|
|
sig, &sigSz, keyBlob, sizeof(keyBlob), 0);
|
|
|
|
cleanup_agent_test(ctx, ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* An identity with no modulus is rejected before the signature buffer
|
|
* is allocated. */
|
|
static void test_wolfSSH_agent_signrequest_rsa_no_modulus(void)
|
|
{
|
|
AssertIntEQ(agent_rsa_sign_result(NULL, 0), WS_BUFFER_E);
|
|
}
|
|
|
|
#if defined(RSA_MAX_SIZE) && (((RSA_MAX_SIZE / 8) + 64) < AGENT_TEST_BUF_SZ)
|
|
/* A modulus larger than wolfCrypt can sign with is rejected the same
|
|
* way. The leading byte keeps the sign bit clear so the value stays a
|
|
* canonical mpint and its length is what the guard sees. */
|
|
static void test_wolfSSH_agent_signrequest_rsa_too_large(void)
|
|
{
|
|
byte modulus[(RSA_MAX_SIZE / 8) + 2];
|
|
|
|
memset(modulus, 0xa5, sizeof(modulus));
|
|
modulus[0] = 0x01;
|
|
AssertIntEQ(agent_rsa_sign_result(modulus, sizeof(modulus)),
|
|
WS_BUFFER_E);
|
|
}
|
|
#endif /* RSA_MAX_SIZE fits AGENT_TEST_BUF_SZ */
|
|
#endif /* WOLFSSH_NO_RSA_SHA2_256 */
|
|
#endif /* WOLFSSH_AGENT */
|
|
|
|
|
|
#ifdef WOLFSSH_OSSH_CERTS
|
|
|
|
/* The committed positive vectors all certify an Ed25519 user key, so the
|
|
* parse/verify tests below require Ed25519. */
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
|
|
/* Non-expiring OpenSSH user certificates over an Ed25519 key with a
|
|
* force-command, one per CA type. Self-verifying: the CA public key is
|
|
* embedded, so parse and verify get covered without ssh-keygen. */
|
|
static const byte ossh_vec_ed[] = {
|
|
0x00, 0x00, 0x00, 0x20, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35,
|
|
0x35, 0x31, 0x39, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x2d, 0x76, 0x30, 0x31,
|
|
0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0x00, 0x00, 0x00, 0x20, 0xcb, 0x18, 0x51, 0x9f, 0x07, 0x48, 0x3d, 0x38,
|
|
0xbc, 0x07, 0xa6, 0xcc, 0x2f, 0x0b, 0x92, 0x9a, 0x5a, 0x45, 0x36, 0x96,
|
|
0x07, 0xc1, 0xc8, 0xc6, 0xf4, 0x4b, 0x7b, 0x59, 0x93, 0x08, 0x64, 0xc4,
|
|
0x00, 0x00, 0x00, 0x20, 0xc1, 0xcd, 0x74, 0xac, 0x52, 0x54, 0xb6, 0x7d,
|
|
0x1a, 0xcf, 0xab, 0xf3, 0x96, 0x90, 0x8a, 0xed, 0xea, 0x93, 0x1c, 0xdc,
|
|
0xb7, 0x31, 0x73, 0x7d, 0xda, 0x3d, 0xd2, 0x5d, 0x0c, 0xcc, 0x41, 0x67,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x06, 0x76, 0x65, 0x63, 0x2d, 0x65, 0x64, 0x00, 0x00,
|
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x66, 0x72, 0x65, 0x64, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x66, 0x6f,
|
|
0x72, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x00,
|
|
0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x65, 0x63, 0x68, 0x6f, 0x20,
|
|
0x68, 0x69, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x15, 0x70, 0x65,
|
|
0x72, 0x6d, 0x69, 0x74, 0x2d, 0x58, 0x31, 0x31, 0x2d, 0x66, 0x6f, 0x72,
|
|
0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x17, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x61, 0x67,
|
|
0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69,
|
|
0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x70, 0x65,
|
|
0x72, 0x6d, 0x69, 0x74, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x66, 0x6f,
|
|
0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x70,
|
|
0x74, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x70, 0x65,
|
|
0x72, 0x6d, 0x69, 0x74, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x72, 0x63,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33,
|
|
0x00, 0x00, 0x00, 0x0b, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35,
|
|
0x35, 0x31, 0x39, 0x00, 0x00, 0x00, 0x20, 0x19, 0x7f, 0x5a, 0xb7, 0x58,
|
|
0xfc, 0x3f, 0x57, 0xbd, 0x59, 0x77, 0x07, 0xe6, 0x97, 0x72, 0x27, 0xc8,
|
|
0xa0, 0xe6, 0xa2, 0x07, 0xd7, 0xb0, 0xf7, 0x67, 0xe2, 0xc7, 0x50, 0x75,
|
|
0x45, 0x96, 0x7d, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x0b, 0x73,
|
|
0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x00, 0x00,
|
|
0x00, 0x40, 0xc1, 0xb0, 0xee, 0x8a, 0xb3, 0xda, 0xbf, 0x87, 0x00, 0x9c,
|
|
0xf1, 0x06, 0xdd, 0x57, 0xce, 0x2e, 0x33, 0xd7, 0xe9, 0x6f, 0x88, 0x6b,
|
|
0xd0, 0x29, 0xcc, 0x90, 0xde, 0x0e, 0x48, 0x98, 0xd1, 0x3a, 0x66, 0x8b,
|
|
0xe2, 0x61, 0x69, 0x0a, 0x78, 0xe2, 0x05, 0x59, 0x4c, 0x8e, 0x00, 0x3f,
|
|
0xed, 0x9f, 0x3d, 0x38, 0xea, 0x27, 0x37, 0xe8, 0x2a, 0x64, 0xe2, 0x73,
|
|
0xc9, 0xbd, 0x7f, 0xc0, 0x78, 0x0c
|
|
};
|
|
|
|
/* CA signature is rsa-sha2-512. */
|
|
#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_RSA_SHA2_512)
|
|
#define WOLFSSH_TEST_OSSH_VEC_RSA
|
|
static const byte ossh_vec_rsa[] = {
|
|
0x00, 0x00, 0x00, 0x20, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35,
|
|
0x35, 0x31, 0x39, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x2d, 0x76, 0x30, 0x31,
|
|
0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0x00, 0x00, 0x00, 0x20, 0xeb, 0xfa, 0x09, 0x63, 0xae, 0x0c, 0xaf, 0xd5,
|
|
0x04, 0xf0, 0xbc, 0x8f, 0xe1, 0x7f, 0x71, 0x66, 0x34, 0x56, 0x87, 0xce,
|
|
0x3e, 0xce, 0xbf, 0x0f, 0xd5, 0x1a, 0xe0, 0x05, 0x33, 0xc3, 0xf1, 0x97,
|
|
0x00, 0x00, 0x00, 0x20, 0xc1, 0xcd, 0x74, 0xac, 0x52, 0x54, 0xb6, 0x7d,
|
|
0x1a, 0xcf, 0xab, 0xf3, 0x96, 0x90, 0x8a, 0xed, 0xea, 0x93, 0x1c, 0xdc,
|
|
0xb7, 0x31, 0x73, 0x7d, 0xda, 0x3d, 0xd2, 0x5d, 0x0c, 0xcc, 0x41, 0x67,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x07, 0x76, 0x65, 0x63, 0x2d, 0x72, 0x73, 0x61, 0x00,
|
|
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x66, 0x72, 0x65, 0x64, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x66,
|
|
0x6f, 0x72, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
|
0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x65, 0x63, 0x68, 0x6f,
|
|
0x20, 0x68, 0x69, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x15, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x58, 0x31, 0x31, 0x2d, 0x66, 0x6f,
|
|
0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x17, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x61,
|
|
0x67, 0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
|
|
0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x66,
|
|
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d,
|
|
0x70, 0x74, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x72,
|
|
0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x17, 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, 0x72, 0x73, 0x61,
|
|
0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
|
|
0xaf, 0xce, 0x0f, 0xf3, 0x04, 0x75, 0xd3, 0x23, 0x93, 0x75, 0x08, 0x5f,
|
|
0x35, 0xfa, 0x87, 0x23, 0xf0, 0x88, 0xce, 0x15, 0xc0, 0x68, 0xec, 0x4f,
|
|
0x5b, 0x6d, 0x9d, 0x8f, 0x59, 0xc5, 0x16, 0x59, 0xdc, 0x0f, 0x28, 0xd7,
|
|
0x92, 0x29, 0xfd, 0xf5, 0x80, 0xf3, 0x6f, 0xa4, 0xfc, 0xb3, 0x75, 0x84,
|
|
0xe3, 0x5d, 0xf0, 0x84, 0xea, 0x15, 0x1c, 0x48, 0xec, 0x91, 0x25, 0x61,
|
|
0x22, 0xc8, 0xa0, 0x74, 0xaf, 0x86, 0xc7, 0xb8, 0x0d, 0x6a, 0x17, 0x51,
|
|
0x57, 0xda, 0x75, 0x9f, 0x7b, 0x7a, 0x3f, 0x7f, 0xd1, 0xcd, 0x64, 0xc6,
|
|
0x89, 0xcf, 0x93, 0xfc, 0x49, 0x0f, 0x00, 0x82, 0x8c, 0xec, 0x8f, 0xd6,
|
|
0x71, 0x23, 0x83, 0x35, 0x3a, 0x75, 0x95, 0x26, 0x36, 0x60, 0x10, 0xd0,
|
|
0xfb, 0xd0, 0x3d, 0x3a, 0x5b, 0xb8, 0x9d, 0x68, 0x3f, 0x3c, 0xfc, 0xbc,
|
|
0x44, 0x6c, 0xb7, 0x7d, 0x42, 0xed, 0xcd, 0xde, 0xd2, 0xfb, 0xd5, 0xbd,
|
|
0x64, 0x35, 0xee, 0xf6, 0xf7, 0xe0, 0x09, 0xa6, 0xa5, 0x9b, 0x7a, 0x11,
|
|
0xd9, 0xae, 0x7f, 0x86, 0x48, 0x7d, 0x77, 0x14, 0xc1, 0xbd, 0x2b, 0xbd,
|
|
0x86, 0xf4, 0x23, 0x9a, 0x7a, 0x07, 0xa8, 0xdd, 0x11, 0x85, 0x43, 0xa3,
|
|
0xb0, 0xcd, 0x9f, 0xfb, 0x77, 0x7e, 0x8d, 0x5d, 0xd1, 0xc0, 0x48, 0x9b,
|
|
0x7a, 0x79, 0x2f, 0x8f, 0x4b, 0xa3, 0xb7, 0xe6, 0x10, 0x71, 0xde, 0x0c,
|
|
0xa3, 0xf6, 0x71, 0xe9, 0xb4, 0x54, 0xd9, 0x82, 0x64, 0xf6, 0x76, 0x70,
|
|
0x84, 0xab, 0xfc, 0xf3, 0x73, 0x1e, 0xc7, 0x60, 0xaa, 0x43, 0xbf, 0x59,
|
|
0xbe, 0x1e, 0xc6, 0x5b, 0xda, 0x3e, 0x5c, 0x9a, 0xaa, 0xb7, 0x78, 0xbc,
|
|
0x71, 0x24, 0x57, 0xad, 0xce, 0x1b, 0xf4, 0x84, 0x29, 0xa4, 0xaa, 0x22,
|
|
0x3a, 0x80, 0xc6, 0x9f, 0x64, 0xfe, 0xd2, 0xcf, 0x87, 0x51, 0xa4, 0xcc,
|
|
0x4e, 0x76, 0x3c, 0xf5, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x0c,
|
|
0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32,
|
|
0x00, 0x00, 0x01, 0x00, 0x3a, 0x8e, 0x29, 0xbb, 0xb8, 0x5f, 0xef, 0x07,
|
|
0x12, 0x31, 0xd7, 0xc9, 0xd6, 0x88, 0xdf, 0x10, 0xcd, 0x79, 0x54, 0x0c,
|
|
0x17, 0xab, 0xbf, 0xde, 0x00, 0xdb, 0x35, 0x7a, 0x0d, 0x67, 0x7d, 0x27,
|
|
0xaf, 0x69, 0xa2, 0x3d, 0xaf, 0x44, 0xf8, 0xcd, 0xf9, 0xcc, 0xd9, 0xb2,
|
|
0x01, 0xfd, 0xa6, 0x7a, 0x44, 0x4a, 0xcc, 0x77, 0xf8, 0xb8, 0xa4, 0xc2,
|
|
0x5a, 0x0d, 0x03, 0x73, 0x71, 0x26, 0xf5, 0xf0, 0x24, 0x7d, 0xb9, 0xf4,
|
|
0x99, 0x1b, 0xc5, 0xa0, 0x23, 0x12, 0xae, 0xe2, 0x0d, 0x36, 0xf5, 0x9c,
|
|
0xf4, 0xba, 0xa4, 0x96, 0xaa, 0x57, 0xeb, 0xf4, 0x38, 0x79, 0x6e, 0xad,
|
|
0x0c, 0x8a, 0x81, 0x6a, 0xc9, 0xda, 0xe7, 0x4a, 0x81, 0x5e, 0x4c, 0x61,
|
|
0x64, 0x04, 0x3d, 0x0b, 0x29, 0x51, 0x91, 0x1f, 0xb5, 0x31, 0x5f, 0xf7,
|
|
0x12, 0x95, 0xe9, 0x9b, 0x9b, 0x8e, 0xf8, 0x66, 0x48, 0xbd, 0x84, 0x4c,
|
|
0x29, 0x91, 0x6d, 0xbd, 0x09, 0x72, 0xfd, 0x18, 0x83, 0x8c, 0xd4, 0x38,
|
|
0x36, 0x7f, 0x1f, 0x31, 0x02, 0xc3, 0x83, 0xb0, 0x24, 0x24, 0x47, 0xca,
|
|
0x2e, 0xf8, 0xe2, 0xf0, 0x70, 0x04, 0x31, 0x03, 0x74, 0x38, 0xd5, 0xce,
|
|
0x85, 0x39, 0x39, 0x38, 0x87, 0x34, 0x75, 0xc1, 0x63, 0x4a, 0x1a, 0xed,
|
|
0x7d, 0xa9, 0x8b, 0xee, 0x37, 0xf0, 0x45, 0xcb, 0x89, 0xb4, 0xce, 0x36,
|
|
0x74, 0xd7, 0x04, 0x02, 0xcf, 0xad, 0x62, 0x21, 0x81, 0x1a, 0xc9, 0xf0,
|
|
0x25, 0x91, 0xa7, 0xcb, 0xbe, 0xe0, 0xa8, 0x8a, 0x03, 0x3b, 0x20, 0xa7,
|
|
0xb4, 0x72, 0xbe, 0xc2, 0xe6, 0x33, 0xd2, 0x5c, 0xc8, 0xeb, 0xba, 0x17,
|
|
0xf6, 0x30, 0x59, 0x9f, 0x73, 0xc0, 0xee, 0xbf, 0xcc, 0x36, 0xa9, 0xf0,
|
|
0x7c, 0xcf, 0x17, 0x9f, 0x07, 0x26, 0x41, 0xc1, 0x8f, 0x44, 0x67, 0xf6,
|
|
0xc4, 0x56, 0x49, 0x95, 0x04, 0x3d, 0xc4, 0x7d
|
|
};
|
|
#endif /* RSA && RSA_SHA2_512 */
|
|
|
|
/* CA signature is rsa-sha2-256. */
|
|
#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_RSA_SHA2_256)
|
|
#define WOLFSSH_TEST_OSSH_VEC_RSA256
|
|
static const byte ossh_vec_rsa256[] = {
|
|
0x00, 0x00, 0x00, 0x20, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35,
|
|
0x35, 0x31, 0x39, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x2d, 0x76, 0x30, 0x31,
|
|
0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0x00, 0x00, 0x00, 0x20, 0x58, 0x46, 0x32, 0x1c, 0xaa, 0xf8, 0xbb, 0xdb,
|
|
0xe8, 0x78, 0x0f, 0x5a, 0x58, 0x9c, 0x5d, 0x6a, 0x19, 0xe6, 0xbe, 0x0c,
|
|
0xf3, 0xaa, 0x5f, 0x8b, 0x78, 0x98, 0xc8, 0xb3, 0x7d, 0xd7, 0x5c, 0xbf,
|
|
0x00, 0x00, 0x00, 0x20, 0x4f, 0x71, 0x7b, 0x74, 0x50, 0x6d, 0xe7, 0x32,
|
|
0x6c, 0xb3, 0x70, 0x3b, 0x3a, 0x02, 0x15, 0x99, 0x96, 0xc5, 0x9d, 0x56,
|
|
0x56, 0xaa, 0xdb, 0x90, 0x21, 0x35, 0x4a, 0x14, 0x61, 0x9d, 0x7e, 0x1c,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x0a, 0x76, 0x65, 0x63, 0x2d, 0x72, 0x73, 0x61, 0x32,
|
|
0x35, 0x36, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x66, 0x72,
|
|
0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
|
|
0x00, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d,
|
|
0x61, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x65,
|
|
0x63, 0x68, 0x6f, 0x20, 0x68, 0x69, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00,
|
|
0x00, 0x15, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x58, 0x31, 0x31,
|
|
0x2d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x70, 0x65, 0x72, 0x6d, 0x69,
|
|
0x74, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x77,
|
|
0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x16, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x70, 0x6f, 0x72,
|
|
0x74, 0x2d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x70, 0x65, 0x72, 0x6d,
|
|
0x69, 0x74, 0x2d, 0x70, 0x74, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x75, 0x73, 0x65,
|
|
0x72, 0x2d, 0x72, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x01, 0x17, 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d,
|
|
0x72, 0x73, 0x61, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x01, 0x00, 0x00,
|
|
0x01, 0x01, 0x00, 0xc5, 0x6e, 0x8e, 0xdb, 0xab, 0x23, 0xad, 0x8e, 0x49,
|
|
0x9e, 0x4e, 0x34, 0x9c, 0x23, 0x4b, 0xc0, 0x8d, 0xe1, 0xea, 0x91, 0x36,
|
|
0x8d, 0x3b, 0x18, 0x70, 0xd1, 0x2c, 0xa5, 0x31, 0x02, 0x4f, 0xdc, 0x16,
|
|
0xe9, 0x0a, 0x0e, 0x88, 0xd1, 0x9d, 0x6d, 0x7f, 0xa2, 0xb6, 0x00, 0x0f,
|
|
0x85, 0xa0, 0x0e, 0xdb, 0x8d, 0x50, 0x47, 0x02, 0xca, 0x9a, 0x2f, 0xc6,
|
|
0x46, 0x50, 0x6e, 0xd0, 0xc5, 0x7e, 0x7a, 0xe5, 0xae, 0x89, 0xb6, 0xe1,
|
|
0xd4, 0xf6, 0x7b, 0xbe, 0xad, 0x9e, 0x50, 0xc3, 0xed, 0x2e, 0xa7, 0xd5,
|
|
0x8f, 0xdc, 0x63, 0x31, 0xbd, 0xe6, 0xce, 0xc4, 0x7f, 0x7d, 0xde, 0x46,
|
|
0x55, 0x2b, 0x11, 0x2c, 0xc3, 0x2d, 0x91, 0x3d, 0xc1, 0xee, 0xbc, 0x23,
|
|
0x0c, 0x5a, 0x12, 0xfb, 0xc2, 0xa6, 0x34, 0xd6, 0x74, 0x3c, 0xcb, 0x29,
|
|
0xcc, 0x9e, 0x44, 0x52, 0x10, 0x08, 0x93, 0xd7, 0xfa, 0x80, 0xaf, 0x29,
|
|
0x51, 0x57, 0x76, 0xd9, 0xb0, 0x56, 0xcf, 0x0d, 0x0a, 0xe9, 0xb2, 0x3d,
|
|
0x2d, 0xb0, 0x1d, 0x99, 0x12, 0x87, 0x96, 0x14, 0x1f, 0x31, 0x11, 0x80,
|
|
0x1a, 0x05, 0x24, 0x05, 0x5f, 0xea, 0xe5, 0x2d, 0x5f, 0x79, 0x5e, 0x41,
|
|
0x63, 0xc7, 0x75, 0x7e, 0xe6, 0xe3, 0x1f, 0x9f, 0x7e, 0x1c, 0xf5, 0xc6,
|
|
0xbc, 0xb9, 0x51, 0xb5, 0xcc, 0x00, 0x99, 0x5c, 0x21, 0x99, 0xa5, 0xbb,
|
|
0x12, 0x69, 0xc2, 0x2b, 0x1d, 0x6b, 0x1f, 0x5f, 0xcb, 0x0d, 0x9b, 0x52,
|
|
0x3b, 0xdc, 0x75, 0x28, 0x80, 0xca, 0x4b, 0x24, 0x95, 0x57, 0xae, 0xef,
|
|
0x8c, 0x26, 0x75, 0x11, 0x88, 0xdb, 0x1e, 0x48, 0x1f, 0x43, 0x06, 0xab,
|
|
0x0d, 0xed, 0x29, 0x87, 0xa7, 0xc7, 0x6f, 0x51, 0xc3, 0x4d, 0xbd, 0x78,
|
|
0x10, 0x6a, 0xb7, 0x24, 0x3f, 0x70, 0x93, 0x34, 0x54, 0xe4, 0x04, 0x5e,
|
|
0x14, 0xc6, 0x03, 0x82, 0x6a, 0x84, 0xf3, 0x00, 0x00, 0x01, 0x14, 0x00,
|
|
0x00, 0x00, 0x0c, 0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d,
|
|
0x32, 0x35, 0x36, 0x00, 0x00, 0x01, 0x00, 0x6a, 0xa8, 0xfa, 0x0c, 0x70,
|
|
0x32, 0xc2, 0xb9, 0x61, 0x6a, 0x3b, 0x8f, 0xca, 0x6a, 0xe2, 0xb8, 0xf5,
|
|
0xeb, 0xe2, 0x18, 0x9e, 0x87, 0x51, 0x25, 0xa4, 0x77, 0x31, 0x84, 0x9b,
|
|
0x1c, 0x3a, 0x59, 0x8e, 0xc5, 0x29, 0xe0, 0x42, 0x8d, 0xb4, 0x40, 0xbe,
|
|
0x46, 0x89, 0x73, 0x60, 0x6b, 0x93, 0x83, 0x78, 0x7a, 0x15, 0x68, 0x52,
|
|
0xef, 0xc4, 0xc3, 0x11, 0x6e, 0xb3, 0xb0, 0x2e, 0xa8, 0xb0, 0x2b, 0xac,
|
|
0xd6, 0x4c, 0x87, 0x08, 0x5c, 0xa0, 0xac, 0x40, 0x9e, 0x88, 0xe9, 0x34,
|
|
0xcc, 0x1a, 0xa3, 0x17, 0xeb, 0x45, 0xed, 0x2c, 0x86, 0xd2, 0x7c, 0x0f,
|
|
0xf5, 0x6d, 0xa2, 0x77, 0xaf, 0x3b, 0x96, 0x54, 0x24, 0xaa, 0x8a, 0x0a,
|
|
0x92, 0x3b, 0x84, 0xc2, 0xd7, 0xc0, 0xae, 0x83, 0xe8, 0xe9, 0x54, 0x07,
|
|
0xdb, 0x4f, 0x5a, 0x0f, 0x60, 0x9a, 0xa4, 0x84, 0xe8, 0xbb, 0xc3, 0x4d,
|
|
0x71, 0xa3, 0x75, 0x0c, 0x83, 0xa1, 0xc9, 0x59, 0x85, 0xca, 0x2e, 0xd7,
|
|
0x88, 0xa4, 0xaa, 0xe2, 0x27, 0x0c, 0x8a, 0x23, 0x5b, 0xbf, 0x3f, 0xb5,
|
|
0x57, 0x56, 0xae, 0xa9, 0x10, 0xd0, 0x4e, 0x2a, 0x14, 0x10, 0xdf, 0xa7,
|
|
0x32, 0x30, 0x89, 0xaa, 0x92, 0xc1, 0xfc, 0x49, 0x28, 0xde, 0x04, 0x71,
|
|
0xf2, 0xf2, 0x2e, 0xe9, 0x17, 0xed, 0xdd, 0x99, 0x6a, 0x93, 0x7a, 0x3e,
|
|
0x0a, 0xb0, 0x75, 0x44, 0x58, 0x73, 0x1f, 0xb3, 0x9f, 0xa9, 0xef, 0xf3,
|
|
0x88, 0x48, 0x9c, 0x61, 0xd6, 0xc0, 0x12, 0x65, 0x61, 0x35, 0xbd, 0xdc,
|
|
0x05, 0x8d, 0x95, 0xce, 0x9b, 0x68, 0x7d, 0xcc, 0x71, 0xad, 0x56, 0x00,
|
|
0x0d, 0x4e, 0x5a, 0xeb, 0x41, 0x11, 0xc5, 0x47, 0x72, 0x59, 0x93, 0xc4,
|
|
0xac, 0xd6, 0xea, 0x47, 0xdb, 0x58, 0xb8, 0x51, 0x8f, 0x95, 0xd7, 0x03,
|
|
0x2c, 0x96, 0xc6, 0x19, 0xa0, 0x17, 0x71, 0x2a, 0x5c, 0x97, 0x8f
|
|
};
|
|
#endif /* RSA && RSA_SHA2_256 */
|
|
|
|
/* CA signature is ecdsa-sha2-nistp256. */
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
#define WOLFSSH_TEST_OSSH_VEC_ECC
|
|
static const byte ossh_vec_ecc[] = {
|
|
0x00, 0x00, 0x00, 0x20, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35,
|
|
0x35, 0x31, 0x39, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x2d, 0x76, 0x30, 0x31,
|
|
0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0x00, 0x00, 0x00, 0x20, 0x70, 0x73, 0x37, 0x68, 0x54, 0x47, 0x3a, 0x25,
|
|
0xe0, 0xc7, 0xd1, 0xfa, 0x68, 0xc8, 0xe6, 0x76, 0xb3, 0xd9, 0x88, 0x82,
|
|
0x4e, 0x29, 0xaa, 0xbf, 0x7e, 0xa6, 0x9c, 0xd5, 0x7f, 0xeb, 0x7d, 0x3e,
|
|
0x00, 0x00, 0x00, 0x20, 0xc1, 0xcd, 0x74, 0xac, 0x52, 0x54, 0xb6, 0x7d,
|
|
0x1a, 0xcf, 0xab, 0xf3, 0x96, 0x90, 0x8a, 0xed, 0xea, 0x93, 0x1c, 0xdc,
|
|
0xb7, 0x31, 0x73, 0x7d, 0xda, 0x3d, 0xd2, 0x5d, 0x0c, 0xcc, 0x41, 0x67,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x07, 0x76, 0x65, 0x63, 0x2d, 0x65, 0x63, 0x63, 0x00,
|
|
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x66, 0x72, 0x65, 0x64, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x66,
|
|
0x6f, 0x72, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
|
0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x65, 0x63, 0x68, 0x6f,
|
|
0x20, 0x68, 0x69, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x15, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x58, 0x31, 0x31, 0x2d, 0x66, 0x6f,
|
|
0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x17, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x61,
|
|
0x67, 0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
|
|
0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x66,
|
|
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d,
|
|
0x70, 0x74, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x70,
|
|
0x65, 0x72, 0x6d, 0x69, 0x74, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x72,
|
|
0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x68, 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, 0x45, 0x2d, 0xd1, 0x42, 0x5a, 0x47, 0xeb,
|
|
0x27, 0x6f, 0x78, 0xe4, 0xa8, 0x9e, 0x4e, 0x93, 0x21, 0x68, 0xff, 0xfb,
|
|
0x7b, 0x06, 0x31, 0xf2, 0x30, 0xb7, 0xb0, 0x7e, 0xe2, 0x28, 0xc2, 0x18,
|
|
0x99, 0x51, 0x24, 0xac, 0x55, 0x2b, 0xb3, 0x7f, 0xdb, 0x71, 0x5c, 0x34,
|
|
0x68, 0xe0, 0x12, 0x06, 0x5a, 0x01, 0x12, 0xd0, 0x1b, 0x62, 0x19, 0x1c,
|
|
0x14, 0xe3, 0xf0, 0xcb, 0xe8, 0xf5, 0x6c, 0xae, 0x70, 0x00, 0x00, 0x00,
|
|
0x64, 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, 0x49, 0x00, 0x00, 0x00, 0x21, 0x00, 0xc9, 0x89, 0xf5,
|
|
0xc2, 0xe1, 0x51, 0x4e, 0x2a, 0x68, 0x9b, 0xf1, 0x07, 0x17, 0xf1, 0x07,
|
|
0x49, 0xbc, 0x28, 0xf2, 0x79, 0x71, 0xf9, 0x8b, 0xb3, 0x08, 0x51, 0x38,
|
|
0x17, 0xb8, 0x5f, 0x8c, 0xe7, 0x00, 0x00, 0x00, 0x20, 0x70, 0xfa, 0xfa,
|
|
0xf1, 0x0e, 0xd5, 0x70, 0x22, 0x47, 0xea, 0x6a, 0x7c, 0xa6, 0x98, 0xc3,
|
|
0x2c, 0x9e, 0xca, 0x2c, 0xb5, 0xe0, 0xcc, 0x46, 0x36, 0x6a, 0xb7, 0x8c,
|
|
0x49, 0xda, 0x40, 0x97, 0x30
|
|
};
|
|
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
|
|
|
#ifdef WOLFSSL_BASE64_ENCODE
|
|
|
|
/* Renders a blob as the "name base64" line an authorized_keys style file
|
|
* holds. Returns the length written to line. */
|
|
static word32 buildOsshCertLine(byte* line, word32 lineCap,
|
|
const byte* vec, word32 vecSz)
|
|
{
|
|
static const char name[] = "ssh-ed25519-cert-v01@openssh.com ";
|
|
word32 nameSz, b64Sz;
|
|
|
|
nameSz = (word32)WSTRLEN(name);
|
|
AssertIntGT(lineCap, nameSz + 2);
|
|
WMEMCPY(line, name, nameSz);
|
|
|
|
b64Sz = lineCap - nameSz - 2;
|
|
AssertIntEQ(0, Base64_Encode_NoNl(vec, vecSz, line + nameSz, &b64Sz));
|
|
line[nameSz + b64Sz] = '\n';
|
|
|
|
return nameSz + b64Sz + 1;
|
|
}
|
|
|
|
|
|
/* Reads a certificate vector back through the public API in the "name base64"
|
|
* line form an authorized_keys style file holds. */
|
|
static void test_wolfSSH_ReadCert_buffer_ossh(void)
|
|
{
|
|
byte line[2048];
|
|
byte* out = NULL;
|
|
const byte* outType = NULL;
|
|
word32 outSz = 0, outTypeSz = 0, lineSz;
|
|
byte flavor = 0xFF;
|
|
|
|
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
|
|
ossh_vec_ed, (word32)sizeof(ossh_vec_ed));
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(line, lineSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntEQ(outSz, (word32)sizeof(ossh_vec_ed));
|
|
AssertIntEQ(0, WMEMCMP(out, ossh_vec_ed, sizeof(ossh_vec_ed)));
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_OSSH);
|
|
AssertStrEQ((const char*)outType, "ssh-ed25519-cert-v01@openssh.com");
|
|
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
out = NULL;
|
|
|
|
/* Half a blob still forms a valid line, so the parse must reject it. The
|
|
* line names its algorithm, so outType is set before that parse runs and
|
|
* has to be taken back with the rest. */
|
|
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
|
|
ossh_vec_ed, (word32)sizeof(ossh_vec_ed) / 2);
|
|
|
|
AssertIntLT(wolfSSH_ReadCert_buffer(line, lineSz,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL), 0);
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
}
|
|
|
|
#else
|
|
|
|
static void test_wolfSSH_ReadCert_buffer_ossh(void) { ; }
|
|
|
|
#endif /* WOLFSSL_BASE64_ENCODE */
|
|
|
|
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
|
|
|
|
/* Reads the same line form from a file. This is the only certificate the
|
|
* file reader takes in a build without X.509 support. */
|
|
static void test_wolfSSH_ReadCert_file_ossh(void)
|
|
{
|
|
byte* out = NULL;
|
|
const byte* outType = NULL;
|
|
word32 outSz = 0, outTypeSz = 0;
|
|
byte flavor = 0xFF;
|
|
#ifdef WOLFSSL_BASE64_ENCODE
|
|
byte line[2048];
|
|
word32 lineSz;
|
|
|
|
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
|
|
ossh_vec_ed, (word32)sizeof(ossh_vec_ed));
|
|
AssertIntEQ(0, writeTmpFile(osshCertPath, line, lineSz));
|
|
|
|
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file(osshCertPath,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
|
|
AssertNotNull(out);
|
|
AssertIntEQ(outSz, (word32)sizeof(ossh_vec_ed));
|
|
AssertIntEQ(0, WMEMCMP(out, ossh_vec_ed, sizeof(ossh_vec_ed)));
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_OSSH);
|
|
AssertStrEQ((const char*)outType, "ssh-ed25519-cert-v01@openssh.com");
|
|
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
|
|
WFREE(out, NULL, DYNTYPE_CERT);
|
|
out = NULL;
|
|
AssertIntEQ(0, remove(osshCertPath));
|
|
#endif /* WOLFSSL_BASE64_ENCODE */
|
|
|
|
/* The name only routes the file; the blob still has to parse. Rejecting
|
|
* it clears the name the line had already supplied. */
|
|
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
|
|
WSTRLEN(osshCertLine)));
|
|
AssertIntLT(wolfSSH_ReadCert_file(osshCertPath,
|
|
&out, &outSz, &outType, &outTypeSz, &flavor, NULL), 0);
|
|
AssertNull(out);
|
|
AssertIntEQ(outSz, 0);
|
|
AssertNull(outType);
|
|
AssertIntEQ(outTypeSz, 0);
|
|
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
|
|
AssertIntEQ(0, remove(osshCertPath));
|
|
}
|
|
|
|
#else
|
|
|
|
static void test_wolfSSH_ReadCert_file_ossh(void) { ; }
|
|
|
|
#endif /* WOLFSSH_TEST_OSSH_CERT_FILE */
|
|
|
|
/* Parse, verify the CA signature, and validate the options of each committed
|
|
* certificate vector; then flip the final signature byte and confirm the
|
|
* verification fails while the parse still succeeds. */
|
|
static void test_wolfSSH_OsshCert_valid(void)
|
|
{
|
|
const byte* blobs[4];
|
|
word32 sizes[4];
|
|
byte tampered[1024];
|
|
WS_OsshCert cert;
|
|
int v;
|
|
int n = 0;
|
|
|
|
/* Each vector is signed by a different CA, so a vector is only usable
|
|
* when its CA algorithm is built in. */
|
|
blobs[n] = ossh_vec_ed;
|
|
sizes[n] = (word32)sizeof(ossh_vec_ed);
|
|
n++;
|
|
#ifdef WOLFSSH_TEST_OSSH_VEC_RSA
|
|
blobs[n] = ossh_vec_rsa;
|
|
sizes[n] = (word32)sizeof(ossh_vec_rsa);
|
|
n++;
|
|
#endif
|
|
#ifdef WOLFSSH_TEST_OSSH_VEC_RSA256
|
|
blobs[n] = ossh_vec_rsa256;
|
|
sizes[n] = (word32)sizeof(ossh_vec_rsa256);
|
|
n++;
|
|
#endif
|
|
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
|
|
blobs[n] = ossh_vec_ecc;
|
|
sizes[n] = (word32)sizeof(ossh_vec_ecc);
|
|
n++;
|
|
#endif
|
|
|
|
for (v = 0; v < n; v++) {
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, blobs[v],
|
|
sizes[v]), WS_SUCCESS);
|
|
AssertIntEQ((int)cert.certType, WOLFSSH_OSSH_CERT_TYPE_USER);
|
|
AssertIntEQ(OsshCertCheckType(&cert), WS_SUCCESS);
|
|
AssertIntEQ(OsshCertVerifySignature(&cert, NULL), WS_SUCCESS);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
AssertNotNull(cert.forceCommand);
|
|
|
|
AssertIntLE((int)sizes[v], (int)sizeof(tampered));
|
|
WMEMCPY(tampered, blobs[v], sizes[v]);
|
|
tampered[sizes[v] - 1] ^= 0xFF;
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, tampered,
|
|
sizes[v]), WS_SUCCESS);
|
|
AssertIntNE(OsshCertVerifySignature(&cert, NULL), WS_SUCCESS);
|
|
}
|
|
}
|
|
|
|
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
|
|
/* An ECDSA CA blob names its curve twice, in the key type and in the curve
|
|
* string. Verification must reject a blob whose two names disagree. */
|
|
static void test_wolfSSH_OsshCert_ecc_curve_mismatch(void)
|
|
{
|
|
byte tampered[1024];
|
|
WS_OsshCert cert;
|
|
word32 curveOff;
|
|
|
|
AssertIntLE((int)sizeof(ossh_vec_ecc), (int)sizeof(tampered));
|
|
WMEMCPY(tampered, ossh_vec_ecc, sizeof(ossh_vec_ecc));
|
|
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, tampered,
|
|
(word32)sizeof(ossh_vec_ecc)), WS_SUCCESS);
|
|
AssertIntEQ(OsshCertVerifySignature(&cert, NULL), WS_SUCCESS);
|
|
|
|
/* CA blob is string type, string curve, string Q. */
|
|
curveOff = (word32)(cert.caKey - tampered) + LENGTH_SZ + cert.caKeyTypeSz
|
|
+ LENGTH_SZ;
|
|
AssertIntEQ(WMEMCMP(tampered + curveOff, "nistp256", 8), 0);
|
|
WMEMCPY(tampered + curveOff, "nistp384", 8);
|
|
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, tampered,
|
|
(word32)sizeof(ossh_vec_ecc)), WS_SUCCESS);
|
|
AssertIntEQ(OsshCertVerifySignature(&cert, NULL), WS_INVALID_ALGO_ID);
|
|
}
|
|
#endif /* WOLFSSH_TEST_OSSH_VEC_ECC */
|
|
|
|
/* OsshCertCheckType is the gate that keeps a host certificate, or one signed
|
|
* by an unsupported CA algorithm, out of user authentication. */
|
|
static void test_wolfSSH_OsshCert_checktype(void)
|
|
{
|
|
static const char caDss[] = "ssh-dss";
|
|
WS_OsshCert cert;
|
|
|
|
/* A host certificate presented for user auth must be rejected. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, ossh_vec_ed,
|
|
(word32)sizeof(ossh_vec_ed)), WS_SUCCESS);
|
|
AssertIntEQ(OsshCertCheckType(&cert), WS_SUCCESS);
|
|
cert.certType = WOLFSSH_OSSH_CERT_TYPE_USER + 1;
|
|
AssertIntEQ(OsshCertCheckType(&cert), WS_INVALID_ALGO_ID);
|
|
|
|
/* An unsupported CA key type must be rejected. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, ossh_vec_ed,
|
|
(word32)sizeof(ossh_vec_ed)), WS_SUCCESS);
|
|
cert.caKeyType = (const byte*)caDss;
|
|
cert.caKeyTypeSz = (word32)WSTRLEN(caDss);
|
|
AssertIntEQ(OsshCertCheckType(&cert), WS_INVALID_ALGO_ID);
|
|
|
|
AssertIntEQ(OsshCertCheckType(NULL), WS_BAD_ARGUMENT);
|
|
}
|
|
|
|
/* Parse and verify run on the peer's blob before any CA-trust check, so a
|
|
* malformed or truncated blob must be rejected and must not crash. */
|
|
static void test_wolfSSH_OsshCert_malformed(void)
|
|
{
|
|
static const byte garbage[64] = {
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
WS_OsshCert cert;
|
|
byte caKey[32];
|
|
byte sig[16];
|
|
word32 caTypeSz;
|
|
word32 i;
|
|
int t;
|
|
int caTypeCount = 0;
|
|
/* CA key type names whose bodies are then truncated. Only the CA
|
|
* algorithms this build supports have a verifier to dispatch to. */
|
|
const char* caTypes[3];
|
|
|
|
caTypes[caTypeCount++] = "ssh-ed25519";
|
|
#ifndef WOLFSSH_NO_RSA
|
|
caTypes[caTypeCount++] = "ssh-rsa";
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
caTypes[caTypeCount++] = "ecdsa-sha2-nistp256";
|
|
#endif
|
|
|
|
/* Parser: every truncation of a hostile buffer must fail. */
|
|
WMEMSET(caKey, 0, sizeof(caKey));
|
|
for (i = 0; i <= sizeof(garbage); i++) {
|
|
AssertIntNE(OsshCertParse(&cert, ID_OSSH_CERT_ED25519, garbage, i),
|
|
WS_SUCCESS);
|
|
}
|
|
|
|
/* Verifiers: a CA key blob with only its type string dispatches by CA
|
|
* type, then fails on the truncated body. */
|
|
WMEMSET(sig, 0xA5, sizeof(sig));
|
|
for (t = 0; t < caTypeCount; t++) {
|
|
caTypeSz = (word32)WSTRLEN(caTypes[t]);
|
|
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.blob = garbage;
|
|
cert.blobSz = (word32)sizeof(garbage);
|
|
cert.signedLen = 0;
|
|
cert.caKeyType = (const byte*)caTypes[t];
|
|
cert.caKeyTypeSz = caTypeSz;
|
|
cert.caKey = caKey;
|
|
cert.caKeySz = LENGTH_SZ + caTypeSz; /* type only, body truncated */
|
|
cert.signature = sig;
|
|
cert.signatureSz = (word32)sizeof(sig);
|
|
|
|
AssertIntNE(OsshCertVerifySignature(&cert, NULL), WS_SUCCESS);
|
|
}
|
|
}
|
|
|
|
#endif /* !WOLFSSH_NO_ED25519 */
|
|
|
|
/* Direct coverage for OsshCertCheckOptions: strict critical options vs.
|
|
* tolerant extensions. Layout: a name string, then a data string. */
|
|
static void test_wolfSSH_OsshCert_options(void)
|
|
{
|
|
WS_OsshCert cert;
|
|
|
|
/* force-command="hi" then source-address="x": ascending, well-formed. */
|
|
static const byte critOk[] = {
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x06, 0,0,0,0x02, 'h','i',
|
|
0,0,0,0x0E, 's','o','u','r','c','e','-','a','d','d','r','e','s','s',
|
|
0,0,0,0x05, 0,0,0,0x01, 'x'
|
|
};
|
|
/* source-address before force-command: not ascending. */
|
|
static const byte critOutOfOrder[] = {
|
|
0,0,0,0x0E, 's','o','u','r','c','e','-','a','d','d','r','e','s','s',
|
|
0,0,0,0x05, 0,0,0,0x01, 'x',
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x06, 0,0,0,0x02, 'h','i'
|
|
};
|
|
/* force-command twice: duplicate name. */
|
|
static const byte critDup[] = {
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x06, 0,0,0,0x02, 'h','i',
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x06, 0,0,0,0x02, 'h','i'
|
|
};
|
|
/* unrecognized critical option name: must be rejected. */
|
|
static const byte critUnknown[] = {
|
|
0,0,0,0x07, 'm','a','d','e','-','u','p',
|
|
0,0,0,0x00
|
|
};
|
|
/* force-command value followed by a trailing byte (di != dataSz). */
|
|
static const byte critMalformed[] = {
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x07, 0,0,0,0x02, 'h','i', 'Z'
|
|
};
|
|
/* force-command with an empty inner string: present but zero length. */
|
|
static const byte critEmptyCmd[] = {
|
|
0,0,0,0x0D, 'f','o','r','c','e','-','c','o','m','m','a','n','d',
|
|
0,0,0,0x04, 0,0,0,0x00
|
|
};
|
|
/* source-address with an empty inner string: present but zero length. */
|
|
static const byte critEmptySrc[] = {
|
|
0,0,0,0x0E, 's','o','u','r','c','e','-','a','d','d','r','e','s','s',
|
|
0,0,0,0x04, 0,0,0,0x00
|
|
};
|
|
/* unknown extension with empty data: tolerated (ignored). */
|
|
static const byte extUnknown[] = {
|
|
0,0,0,0x0B, 'm','a','d','e','-','u','p','-','e','x','t',
|
|
0,0,0,0x00
|
|
};
|
|
/* permit-pty before permit-agent-forwarding: not ascending, still ok. */
|
|
static const byte extOutOfOrder[] = {
|
|
0,0,0,0x0A, 'p','e','r','m','i','t','-','p','t','y',
|
|
0,0,0,0x00,
|
|
0,0,0,0x17, 'p','e','r','m','i','t','-','a','g','e','n','t','-',
|
|
'f','o','r','w','a','r','d','i','n','g',
|
|
0,0,0,0x00
|
|
};
|
|
/* permit-pty twice: duplicate extension, still ok. */
|
|
static const byte extDup[] = {
|
|
0,0,0,0x0A, 'p','e','r','m','i','t','-','p','t','y',
|
|
0,0,0,0x00,
|
|
0,0,0,0x0A, 'p','e','r','m','i','t','-','p','t','y',
|
|
0,0,0,0x00
|
|
};
|
|
|
|
/* no options: valid, nothing extracted. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
AssertNull(cert.forceCommand);
|
|
AssertNull(cert.sourceAddress);
|
|
|
|
/* happy path: both options extracted. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critOk;
|
|
cert.critOptsSz = (word32)sizeof(critOk);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
AssertNotNull(cert.forceCommand);
|
|
AssertIntEQ((int)cert.forceCommandSz, 2);
|
|
AssertIntEQ(WMEMCMP(cert.forceCommand, "hi", 2), 0);
|
|
AssertNotNull(cert.sourceAddress);
|
|
AssertIntEQ((int)cert.sourceAddressSz, 1);
|
|
|
|
/* empty force-command cannot restrict the session, so it is rejected
|
|
* rather than silently dropped (which would fall back to a full shell). */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critEmptyCmd;
|
|
cert.critOptsSz = (word32)sizeof(critEmptyCmd);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_PARSE_E);
|
|
|
|
/* empty source-address is likewise rejected, not treated as no
|
|
* restriction. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critEmptySrc;
|
|
cert.critOptsSz = (word32)sizeof(critEmptySrc);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_PARSE_E);
|
|
|
|
/* out-of-order critical options. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critOutOfOrder;
|
|
cert.critOptsSz = (word32)sizeof(critOutOfOrder);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_PARSE_E);
|
|
|
|
/* duplicate critical option. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critDup;
|
|
cert.critOptsSz = (word32)sizeof(critDup);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_PARSE_E);
|
|
|
|
/* unknown critical option. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critUnknown;
|
|
cert.critOptsSz = (word32)sizeof(critUnknown);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_UNIMPLEMENTED_E);
|
|
|
|
/* malformed critical-option data (trailing byte). */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.critOpts = critMalformed;
|
|
cert.critOptsSz = (word32)sizeof(critMalformed);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_PARSE_E);
|
|
|
|
/* unknown extension is ignored. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.extensions = extUnknown;
|
|
cert.extensionsSz = (word32)sizeof(extUnknown);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
|
|
/* out-of-order extensions are tolerated. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.extensions = extOutOfOrder;
|
|
cert.extensionsSz = (word32)sizeof(extOutOfOrder);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
|
|
/* duplicate extensions are tolerated. */
|
|
WMEMSET(&cert, 0, sizeof(cert));
|
|
cert.extensions = extDup;
|
|
cert.extensionsSz = (word32)sizeof(extDup);
|
|
AssertIntEQ(OsshCertCheckOptions(&cert), WS_SUCCESS);
|
|
}
|
|
|
|
|
|
/* OsshCertBaseId maps each OpenSSH certificate algorithm to its base key ID,
|
|
* returning ID_UNKNOWN for anything that is not a certificate ID. */
|
|
static void test_wolfSSH_OsshCert_baseid(void)
|
|
{
|
|
#ifndef WOLFSSH_NO_OSSH_CERT_RSA
|
|
AssertIntEQ(OsshCertBaseId(ID_OSSH_CERT_RSA), ID_SSH_RSA);
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(OsshCertBaseId(ID_OSSH_CERT_ECDSA_SHA2_NISTP256),
|
|
ID_ECDSA_SHA2_NISTP256);
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384
|
|
AssertIntEQ(OsshCertBaseId(ID_OSSH_CERT_ECDSA_SHA2_NISTP384),
|
|
ID_ECDSA_SHA2_NISTP384);
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
|
|
AssertIntEQ(OsshCertBaseId(ID_OSSH_CERT_ECDSA_SHA2_NISTP521),
|
|
ID_ECDSA_SHA2_NISTP521);
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
AssertIntEQ(OsshCertBaseId(ID_OSSH_CERT_ED25519), ID_ED25519);
|
|
#endif
|
|
/* A non-certificate ID has no base mapping. */
|
|
AssertIntEQ(OsshCertBaseId(ID_SSH_RSA), ID_UNKNOWN);
|
|
AssertIntEQ(OsshCertBaseId(ID_UNKNOWN), ID_UNKNOWN);
|
|
}
|
|
|
|
|
|
/* OsshRsaCertSigId picks the client's RSA signature algorithm, preferring
|
|
* SHA2-512 when the peer advertises it and defaulting to SHA2-256. */
|
|
static void test_wolfSSH_OsshCert_rsasigid(void)
|
|
{
|
|
#if !defined(WOLFSSH_NO_RSA_SHA2_256) && !defined(WOLFSSH_NO_RSA_SHA2_512)
|
|
static const byte only256[] = { ID_RSA_SHA2_256 };
|
|
static const byte only512[] = { ID_RSA_SHA2_512 };
|
|
static const byte mixed[] = { ID_SSH_RSA, ID_RSA_SHA2_512 };
|
|
|
|
/* No peer preference falls back to SHA2-256. */
|
|
AssertIntEQ(OsshRsaCertSigId(NULL, 0), ID_RSA_SHA2_256);
|
|
AssertIntEQ(OsshRsaCertSigId(only256, (word32)sizeof(only256)),
|
|
ID_RSA_SHA2_256);
|
|
/* A peer advertising SHA2-512 upgrades the choice. */
|
|
AssertIntEQ(OsshRsaCertSigId(only512, (word32)sizeof(only512)),
|
|
ID_RSA_SHA2_512);
|
|
AssertIntEQ(OsshRsaCertSigId(mixed, (word32)sizeof(mixed)),
|
|
ID_RSA_SHA2_512);
|
|
#endif
|
|
}
|
|
|
|
|
|
#endif /* WOLFSSH_OSSH_CERTS */
|
|
|
|
|
|
#if defined(WOLFSSH_SFTP) && !defined(NO_WOLFSSH_CLIENT) && \
|
|
!defined(SINGLE_THREADED)
|
|
|
|
/* A peer gone at shutdown is not a failure: the send path returns the reset,
|
|
* the recv path returns generic WS_ERROR with the code in wolfSSH_get_error.
|
|
* WS_SOCKET_ERROR_E is generic, so a real shutdown failure is tolerated too. */
|
|
static int AbsorbBenignReset(WOLFSSH* ssh, int ret)
|
|
{
|
|
if (ret == WS_SOCKET_ERROR_E ||
|
|
(ret == WS_ERROR &&
|
|
wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
byte userPassword[256];
|
|
|
|
static int sftpUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
|
|
{
|
|
int ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
|
|
|
|
if (authType == WOLFSSH_USERAUTH_PASSWORD) {
|
|
const char* defaultPassword = (const char*)ctx;
|
|
word32 passwordSz;
|
|
|
|
ret = WOLFSSH_USERAUTH_SUCCESS;
|
|
if (defaultPassword != NULL) {
|
|
passwordSz = (word32)strlen(defaultPassword);
|
|
memcpy(userPassword, defaultPassword, passwordSz);
|
|
}
|
|
else {
|
|
printf("Expecting password set for test cases\n");
|
|
return ret;
|
|
}
|
|
|
|
if (ret == WOLFSSH_USERAUTH_SUCCESS) {
|
|
authData->sf.password.password = userPassword;
|
|
authData->sf.password.passwordSz = passwordSz;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int AcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz,
|
|
void* ctx)
|
|
{
|
|
(void)pubKey;
|
|
(void)pubKeySz;
|
|
(void)ctx;
|
|
return 0;
|
|
}
|
|
|
|
/* Counts keying completions (initial handshake and each rekey) so the test can
|
|
* assert a mid-transfer rekey actually fired. ctx points to an int counter. */
|
|
static void sftpKeyingCompleteCb(void* ctx)
|
|
{
|
|
if (ctx != NULL)
|
|
(*(int*)ctx)++;
|
|
}
|
|
|
|
/* performs connection to port, sets WOLFSSH_CTX and WOLFSSH on success
|
|
* caller needs to free ctx and ssh when done
|
|
*/
|
|
static void sftp_client_connect(WOLFSSH_CTX** ctx, WOLFSSH** ssh, int port)
|
|
{
|
|
SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID;
|
|
SOCKADDR_IN_T clientAddr;
|
|
socklen_t clientAddrSz = sizeof(clientAddr);
|
|
int ret;
|
|
char* host = (char*)wolfSshIp;
|
|
const char* username = "jill";
|
|
const char* password = "upthehill";
|
|
|
|
if (ctx == NULL || ssh == NULL) {
|
|
return;
|
|
}
|
|
|
|
*ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
if (*ctx == NULL) {
|
|
return;
|
|
}
|
|
|
|
wolfSSH_CTX_SetPublicKeyCheck(*ctx, AcceptAnyServerHostKey);
|
|
wolfSSH_SetUserAuth(*ctx, sftpUserAuth);
|
|
*ssh = wolfSSH_new(*ctx);
|
|
if (*ssh == NULL) {
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
return;
|
|
}
|
|
|
|
build_addr(&clientAddr, host, port);
|
|
tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family);
|
|
if (sockFd < 0) {
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz);
|
|
if (ret != 0){
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
wolfSSH_SetUserAuthCtx(*ssh, (void*)password);
|
|
ret = wolfSSH_SetUsername(*ssh, username);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_set_fd(*ssh, (int)sockFd);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SFTP_connect(*ssh);
|
|
|
|
if (ret != WS_SUCCESS){
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* Upper bound on retry iterations for the SFTP helpers below. */
|
|
#define SFTP_MAX_RETRY_TRIES 1000
|
|
|
|
/* Which errors are safe to retry depends on the callee: re-calling one that
|
|
* has already torn down its state reissues a request that may be partly on
|
|
* the wire, which is the desync these loops exist to avoid.
|
|
*
|
|
* wolfSSH_SFTP_Open() and wolfSSH_SFTP_SendWritePacket() keep their state
|
|
* only for these two (src/wolfsftp.c STATE_OPEN_SEND, STATE_OPEN_GETHANDLE,
|
|
* STATE_SEND_WRITE_READ_STATUS). wolfSSH_SFTP_LS() also keeps it on
|
|
* WS_REKEYING. */
|
|
static int sftp_error_keeps_state(int err)
|
|
{
|
|
return err == WS_WANT_READ || err == WS_WANT_WRITE;
|
|
}
|
|
|
|
|
|
/* wolfSSH_SFTP_Remove() and wolfSSH_SFTP_Close() gate on NoticeError() in
|
|
* src/wolfsftp.c instead, which is broader. Mirror it exactly; the two must
|
|
* not drift apart. */
|
|
static int sftp_error_is_notice(int err)
|
|
{
|
|
return sftp_error_keeps_state(err) || err == WS_CHAN_RXD ||
|
|
err == WS_WINDOW_FULL || err == WS_REKEYING;
|
|
}
|
|
|
|
|
|
/* Drive an SFTP operation to completion. Abandoning one part way leaves its
|
|
* response pending on the stream, which the next operation then reads as its
|
|
* own header. Both helpers stop on a terminal error so a real failure is
|
|
* still reported to the caller. */
|
|
static int sftp_retry_remove(WOLFSSH* ssh, char* name)
|
|
{
|
|
int ret = WS_FATAL_ERROR;
|
|
int tries;
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
|
|
ret = wolfSSH_SFTP_Remove(ssh, name);
|
|
if (ret == WS_SUCCESS ||
|
|
!sftp_error_is_notice(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int sftp_retry_close(WOLFSSH* ssh, byte* handle, word32 handleSz)
|
|
{
|
|
int ret = WS_FATAL_ERROR;
|
|
int tries;
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
|
|
ret = wolfSSH_SFTP_Close(ssh, handle, handleSz);
|
|
if (ret == WS_SUCCESS ||
|
|
!sftp_error_is_notice(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* The staged read target is filled with 'a'; every read starts at offset 0,
|
|
* so all returned bytes must be 'a'. A macro so a failure reports the
|
|
* calling read's line. */
|
|
#define SFTP_CHECK_READ_PAYLOAD(out, rxSz) do { \
|
|
int _i; \
|
|
for (_i = 0; _i < (rxSz); _i++) { \
|
|
if ((out)[_i] != 'a') { \
|
|
break; \
|
|
} \
|
|
} \
|
|
AssertIntEQ(_i, (rxSz)); \
|
|
} while (0)
|
|
|
|
|
|
static void test_wolfSSH_SFTP_SendReadPacket(void)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
{
|
|
WS_SFTPNAME* tmp;
|
|
WS_SFTPNAME* current;
|
|
byte handle[WOLFSSH_MAX_HANDLE];
|
|
word32 handleSz = WOLFSSH_MAX_HANDLE;
|
|
const char* currentDir = ".";
|
|
byte* out = NULL;
|
|
int outSz = 18;
|
|
int rxSz;
|
|
const word32 ofst[2] = {0};
|
|
char rdName[] = "wolfssh_5574_read.tmp";
|
|
byte rdHandle[WOLFSSH_MAX_HANDLE];
|
|
word32 rdHandleSz;
|
|
word32 rdOfst[2] = {0, 0};
|
|
word32 rdSz = 0;
|
|
word32 rdChunk;
|
|
/* staging chunk, kept small for constrained targets */
|
|
byte rdData[512];
|
|
int rdTries;
|
|
int rdWrote;
|
|
int rdErr;
|
|
int rdRet;
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
int err;
|
|
int tries;
|
|
int sawPartial;
|
|
int wrRet;
|
|
byte whandle[WOLFSSH_MAX_HANDLE];
|
|
word32 whandleSz;
|
|
byte wrData[32];
|
|
char wrName[] = "wolfssh_5574_write.tmp";
|
|
#endif
|
|
|
|
/* Stage the file to read. Opening the listing's first entry raced
|
|
* with tests/testsuite.test, which creates and removes files in the
|
|
* same directory under "make -j check". The staging is asserted
|
|
* rather than skipped on failure; skipping would drop the read
|
|
* coverage below without failing the test. Note this makes a
|
|
* writable server working directory a prerequisite of the test. */
|
|
WMEMSET(rdData, 'a', sizeof(rdData));
|
|
/* best effort, the file is normally absent */
|
|
(void)sftp_retry_remove(ssh, rdName);
|
|
rdRet = WS_FATAL_ERROR;
|
|
for (rdTries = 0; rdTries < SFTP_MAX_RETRY_TRIES; rdTries++) {
|
|
rdHandleSz = WOLFSSH_MAX_HANDLE;
|
|
rdRet = wolfSSH_SFTP_Open(ssh, rdName,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, rdHandle, &rdHandleSz);
|
|
if (rdRet == WS_SUCCESS) {
|
|
break;
|
|
}
|
|
rdErr = wolfSSH_get_error(ssh);
|
|
if (!sftp_error_keeps_state(rdErr)) {
|
|
break; /* create failed */
|
|
}
|
|
}
|
|
AssertIntEQ(rdRet, WS_SUCCESS);
|
|
|
|
/* one try per full chunk plus slack for transient errors */
|
|
rdTries = (int)(WOLFSSH_MAX_SFTP_RW / sizeof(rdData)) +
|
|
SFTP_MAX_RETRY_TRIES;
|
|
for (; rdTries > 0 && rdSz < WOLFSSH_MAX_SFTP_RW; rdTries--) {
|
|
rdChunk = (word32)sizeof(rdData);
|
|
if (rdChunk > WOLFSSH_MAX_SFTP_RW - rdSz) {
|
|
rdChunk = WOLFSSH_MAX_SFTP_RW - rdSz;
|
|
}
|
|
rdOfst[0] = rdSz;
|
|
rdWrote = wolfSSH_SFTP_SendWritePacket(ssh, rdHandle,
|
|
rdHandleSz, rdOfst, rdData, rdChunk);
|
|
if (rdWrote > 0) {
|
|
rdSz += (word32)rdWrote;
|
|
continue;
|
|
}
|
|
rdErr = wolfSSH_get_error(ssh);
|
|
if (!sftp_error_keeps_state(rdErr)) {
|
|
break; /* unexpected error */
|
|
}
|
|
}
|
|
rdRet = sftp_retry_close(ssh, rdHandle, rdHandleSz);
|
|
AssertIntEQ(rdSz, WOLFSSH_MAX_SFTP_RW);
|
|
AssertIntEQ(rdRet, WS_SUCCESS);
|
|
|
|
current = NULL;
|
|
for (rdTries = 0; rdTries < SFTP_MAX_RETRY_TRIES; rdTries++) {
|
|
current = wolfSSH_SFTP_LS(ssh, (char*)currentDir);
|
|
if (current != NULL) {
|
|
break;
|
|
}
|
|
rdErr = wolfSSH_get_error(ssh);
|
|
if (!sftp_error_keeps_state(rdErr) && rdErr != WS_REKEYING) {
|
|
break;
|
|
}
|
|
}
|
|
AssertNotNull(current);
|
|
tmp = current;
|
|
while (tmp != NULL) {
|
|
if (tmp->fName != NULL && WSTRCMP(tmp->fName, rdName) == 0) {
|
|
break;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
AssertNotNull(tmp);
|
|
|
|
{
|
|
/* The staging above wrote WOLFSSH_MAX_SFTP_RW bytes, and no read
|
|
* below asks for more, so that is the buffer size needed. */
|
|
out = (byte*)malloc(WOLFSSH_MAX_SFTP_RW);
|
|
AssertNotNull(out);
|
|
AssertIntEQ(wolfSSH_SFTP_Open(ssh, tmp->fName, WOLFSSH_FXF_READ,
|
|
NULL, handle, &handleSz), WS_SUCCESS);
|
|
|
|
/*
|
|
* Since errors are negative, and valid return values are greater
|
|
* than 0, the following wolfSSH_SFTP_SendReadPacket() calls
|
|
* shall return greater than 0 and less-than-equal-to the amount
|
|
* requested, outSz. While this endpoint may request any amount of
|
|
* file data, the peer must not respond with more than requested.
|
|
*/
|
|
|
|
/* read 18 bytes */
|
|
outSz = 18;
|
|
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
|
|
ofst, out, outSz);
|
|
AssertIntGT(rxSz, 0);
|
|
AssertIntLE(rxSz, outSz);
|
|
SFTP_CHECK_READ_PAYLOAD(out, rxSz);
|
|
|
|
/* partial read */
|
|
outSz = WOLFSSH_MAX_SFTP_RW / 2;
|
|
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
|
|
ofst, out, outSz);
|
|
if (wolfSSH_get_error(ssh) != WS_REKEYING) {
|
|
AssertIntGT(rxSz, 0);
|
|
AssertIntLE(rxSz, outSz);
|
|
SFTP_CHECK_READ_PAYLOAD(out, rxSz);
|
|
}
|
|
|
|
/* read all */
|
|
outSz = WOLFSSH_MAX_SFTP_RW;
|
|
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
|
|
ofst, out, outSz);
|
|
if (wolfSSH_get_error(ssh) != WS_REKEYING) {
|
|
AssertIntGT(rxSz, 0);
|
|
AssertIntLE(rxSz, outSz);
|
|
SFTP_CHECK_READ_PAYLOAD(out, rxSz);
|
|
}
|
|
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
/* Issue 5574: force partial positive sends of the read request and
|
|
* confirm STATE_SEND_READ_SEND_REQ preserves the buffer (returning
|
|
* WS_WANT_WRITE) instead of freeing it after the first chunk. With
|
|
* a 1-byte cap the request can only drain over many calls, so the
|
|
* read must still complete with uncorrupted data once consumed.
|
|
* Note: this covers the window/max-packet clamped-partial case
|
|
* (idx < sz). The complementary socket back-pressure case, where
|
|
* the buffer reports fully sent but bytes are still queued in the
|
|
* SSH output buffer, is handled by the same
|
|
* wolfSSH_SFTP_buffer_send_finish() OutputPending guard but is
|
|
* not exercised here because loopback writes rarely back-pressure. */
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
outSz = 18;
|
|
rxSz = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
rxSz = wolfSSH_SFTP_SendReadPacket(ssh, handle, handleSz,
|
|
ofst, out, outSz);
|
|
if (rxSz > 0) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
}
|
|
else if (err != WS_WANT_READ && err != WS_REKEYING) {
|
|
break; /* unexpected error */
|
|
}
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1);
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntGT(rxSz, 0);
|
|
AssertIntLE(rxSz, outSz);
|
|
SFTP_CHECK_READ_PAYLOAD(out, rxSz);
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
|
|
free(out);
|
|
/* Results ignored: the reads above are skipped rather than driven
|
|
* to completion on WS_REKEYING, so the stream state here is not
|
|
* known to be clean and a failure would not mean a real fault. */
|
|
(void)sftp_retry_close(ssh, handle, handleSz);
|
|
}
|
|
wolfSSH_SFTPNAME_list_free(current);
|
|
(void)sftp_retry_remove(ssh, rdName);
|
|
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
/* Issue 5574: exercise the partial-send resume path for
|
|
* wolfSSH_SFTP_SendWritePacket. STATE_SEND_WRITE_SEND_HEADER must not
|
|
* advance to STATE_SEND_WRITE_SEND_BODY until the request header is
|
|
* fully sent; otherwise the body is interleaved into a half-written
|
|
* header and corrupts the stream. The 1-byte cap forces the header to
|
|
* drain over many WS_WANT_WRITE returns (the cap only clamps the header
|
|
* buffer_send; the body uses stream_send directly). The target file is
|
|
* created over SFTP so it is independent of the server's working
|
|
* directory, and is removed afterward. Skipped if create is denied. */
|
|
whandleSz = WOLFSSH_MAX_HANDLE;
|
|
WMEMSET(wrData, 'a', sizeof(wrData));
|
|
if (wolfSSH_SFTP_Open(ssh, wrName,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, whandle, &whandleSz) == WS_SUCCESS) {
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
wrRet = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
wrRet = wolfSSH_SFTP_SendWritePacket(ssh, whandle, whandleSz,
|
|
ofst, wrData, (word32)sizeof(wrData));
|
|
if (wrRet > 0) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* unexpected error */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1); /* header drained over partial sends */
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntEQ(wrRet, (int)sizeof(wrData));
|
|
|
|
/* last SFTP calls before the rekey drain and shutdown assert, so
|
|
* do not leave a response pending here */
|
|
(void)sftp_retry_close(ssh, whandle, whandleSz);
|
|
(void)sftp_retry_remove(ssh, wrName);
|
|
}
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
}
|
|
|
|
/* take care of re-keying state before shutdown call */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING) {
|
|
wolfSSH_worker(ssh, NULL);
|
|
}
|
|
|
|
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
/* in cases where highwater mark is really small a re-key could happen */
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
/* close client socket down */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
/* Weird deadlock without this sleep */
|
|
k_sleep(Z_TIMEOUT_TICKS(100));
|
|
#endif
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
}
|
|
|
|
|
|
/* Issue 5574: drive SFTP client request states through the 1-byte send cap (and
|
|
* the stall hook) so the partial-send resume path (wolfSSH_SFTP_buffer_send_
|
|
* finish) is exercised. Each op is run in a bounded loop: with the cap set the
|
|
* request drains over many WS_WANT_WRITE returns and must still complete. Test
|
|
* objects are created and removed over SFTP so the test does not depend on the
|
|
* server's working directory. The whole body is test-only (needs the cap hook).
|
|
*
|
|
* Coverage of the seven sites that call wolfSSH_SFTP_buffer_send_finish:
|
|
* STATE_OPEN_SEND - covered here (Open under cap)
|
|
* STATE_MKDIR_SEND - covered here (MKDIR under cap)
|
|
* STATE_SET_ATR_SEND - covered here (SetSTAT under cap)
|
|
* STATE_RENAME_SEND - covered here (cap + stall sub-cases)
|
|
* STATE_SEND_READ_SEND_REQ - covered by test_wolfSSH_SFTP_SendReadPacket
|
|
* STATE_SEND_WRITE_SEND_HEADER - covered by test_wolfSSH_SFTP_SendReadPacket
|
|
* STATE_RECV_SEND - NOT directly driven (see below)
|
|
*
|
|
* STATE_RECV_SEND is the server side sending a read/dir/stat response from
|
|
* wolfSSH_SFTP_read(). The send cap is a per-WOLFSSH flag, but this test only
|
|
* holds the client ssh; the in-process echoserver's accepted server ssh
|
|
* (threadCtx->ssh in examples/echoserver) has no test hook to set the cap on,
|
|
* and capping the whole server SFTP session risks destabilizing the threaded
|
|
* exchange. STATE_RECV_SEND runs the identical wolfSSH_SFTP_buffer_send_finish
|
|
* logic already exercised by all six client-side cases above, so it is left as
|
|
* a documented coverage gap (residual risk is the call-site wiring only). */
|
|
static void test_wolfSSH_SFTP_PartialSend(void)
|
|
{
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
int err;
|
|
int tries;
|
|
int sawPartial;
|
|
int ret;
|
|
byte handle[WOLFSSH_MAX_HANDLE];
|
|
word32 handleSz;
|
|
WS_SFTP_FILEATRB atr;
|
|
char openName[] = "wolfssh_5574_open.tmp";
|
|
char mkName[] = "wolfssh_5574_mkdir.tmp";
|
|
char atrName[] = "wolfssh_5574_setatr.tmp";
|
|
char renA[] = "wolfssh_5574_rename_a.tmp";
|
|
char renB[] = "wolfssh_5574_rename_b.tmp";
|
|
char renBad[] = "wolfssh_5574_nodir/sub.tmp";
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* STATE_OPEN_SEND: create a file, then open it for read under the cap so
|
|
* the open request must drain over several partial sends. */
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
if (wolfSSH_SFTP_Open(ssh, openName,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, handle, &handleSz) == WS_SUCCESS) {
|
|
wolfSSH_SFTP_Close(ssh, handle, handleSz);
|
|
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
ret = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
ret = wolfSSH_SFTP_Open(ssh, openName, WOLFSSH_FXF_READ,
|
|
NULL, handle, &handleSz);
|
|
if (ret == WS_SUCCESS) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* unexpected error */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1);
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
|
|
wolfSSH_SFTP_Close(ssh, handle, handleSz);
|
|
wolfSSH_SFTP_Remove(ssh, openName);
|
|
}
|
|
|
|
/* STATE_MKDIR_SEND: make a directory under the cap. RMDIR any stale dir
|
|
* first so a leftover from a prior run does not fail the create. */
|
|
wolfSSH_SFTP_RMDIR(ssh, mkName);
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
ret = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
ret = wolfSSH_SFTP_MKDIR(ssh, mkName, NULL);
|
|
if (ret == WS_SUCCESS) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* unexpected error */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1);
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
wolfSSH_SFTP_RMDIR(ssh, mkName);
|
|
|
|
/* STATE_SET_ATR_SEND: SetSTAT needs an existing file and a non-NULL atr.
|
|
* Create the file, STAT it to populate the atr, then SetSTAT under cap. */
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
if (wolfSSH_SFTP_Open(ssh, atrName,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, handle, &handleSz) == WS_SUCCESS) {
|
|
wolfSSH_SFTP_Close(ssh, handle, handleSz);
|
|
|
|
if (wolfSSH_SFTP_STAT(ssh, atrName, &atr) == WS_SUCCESS) {
|
|
/* Send only the permissions back. A port without WTRUNCATE
|
|
* answers a size request with SSH_FX_OP_UNSUPPORTED. */
|
|
atr.flags = WOLFSSH_FILEATRB_PERM;
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
ret = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
ret = wolfSSH_SFTP_SetSTAT(ssh, atrName, &atr);
|
|
if (ret == WS_SUCCESS) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* unexpected error */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1);
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
}
|
|
wolfSSH_SFTP_Remove(ssh, atrName);
|
|
}
|
|
|
|
/* STATE_RENAME_SEND. Rename first STATs the source, so create it over SFTP.
|
|
* Two sub-cases exercise both branches of the state's resume handling. */
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
if (wolfSSH_SFTP_Open(ssh, renA,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, handle, &handleSz) == WS_SUCCESS) {
|
|
wolfSSH_SFTP_Close(ssh, handle, handleSz);
|
|
wolfSSH_SFTP_Remove(ssh, renB); /* clear any stale target */
|
|
|
|
/* (a) clamped-partial: the request drains over many WS_WANT_WRITE
|
|
* returns under the 1-byte cap; renA -> renB. */
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 1), WS_SUCCESS);
|
|
ret = WS_FATAL_ERROR;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
ret = wolfSSH_SFTP_Rename(ssh, renA, renB);
|
|
if (ret == WS_SUCCESS) {
|
|
break;
|
|
}
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* unexpected error */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpSendCap(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1);
|
|
AssertIntLT(tries, 1000);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_SFTP_STAT(ssh, renB, &atr), WS_SUCCESS); /* renA->renB */
|
|
|
|
/* (b) flush-only resume (HIGH-1): the stall hook makes the fully-sent
|
|
* request report as still pending once, so the next buffer_send returns
|
|
* WS_SUCCESS (0) with idx == sz. STATE_RENAME_SEND's `ret < 0` guard
|
|
* must let that fall through to read the server's response, while the
|
|
* old `ret <= 0` guard treated the 0 as terminal and returned WS_SUCCESS
|
|
* without reading the status. Because the request bytes are already on
|
|
* the wire by the resume call, the server performs/answers the request
|
|
* either way, so the only client-visible difference is the return code:
|
|
* rename into a non-existent directory so the server *rejects* it -
|
|
* correct code surfaces the failure; the buggy guard swallows it and
|
|
* returns WS_SUCCESS. This also exercises the OutputPending branch. */
|
|
AssertIntEQ(wolfSSH_TestSftpStallPending(ssh, 1), WS_SUCCESS);
|
|
ret = WS_SUCCESS;
|
|
sawPartial = 0;
|
|
for (tries = 0; tries < 1000; tries++) {
|
|
ret = wolfSSH_SFTP_Rename(ssh, renB, renBad);
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WANT_WRITE) {
|
|
sawPartial = 1;
|
|
continue;
|
|
}
|
|
if (err == WS_WANT_READ || err == WS_REKEYING) {
|
|
continue;
|
|
}
|
|
break; /* terminal: success or a server-rejected failure */
|
|
}
|
|
AssertIntEQ(wolfSSH_TestSftpStallPending(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(sawPartial, 1); /* flush-only resume path was taken */
|
|
AssertIntLT(tries, 1000); /* terminated; did not spin */
|
|
AssertIntNE(ret, WS_SUCCESS); /* server rejection must not be swallowed */
|
|
|
|
wolfSSH_SFTP_Remove(ssh, renA);
|
|
wolfSSH_SFTP_Remove(ssh, renB);
|
|
}
|
|
|
|
/* take care of re-keying state before shutdown call */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING) {
|
|
wolfSSH_worker(ssh, NULL);
|
|
}
|
|
|
|
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
k_sleep(Z_TIMEOUT_TICKS(100));
|
|
#endif
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
}
|
|
|
|
/* Upper bound on non-blocking retry iterations. A legitimate LS/shutdown across
|
|
* forced rekeys completes in well under this; the bound keeps a regression from
|
|
* hanging CI by tripping the AssertNotNull/AssertIntEQ below instead. */
|
|
#define SFTP_REKEY_MAX_TRIES 100
|
|
|
|
static void sftp_rekey_test(int nonBlock)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
int err;
|
|
int tries;
|
|
int kexCount = 0;
|
|
WS_SOCKET_T clientFd;
|
|
WS_SFTPNAME* ls;
|
|
int i;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Count keying completions from here on. The initial handshake already ran
|
|
* inside sftp_client_connect, so kexCount stays 0 until the highwater rekey
|
|
* fires mid-SFTP; the AssertIntGT below then proves it did. */
|
|
wolfSSH_SetKeyingCompletionCb(ctx, sftpKeyingCompleteCb);
|
|
wolfSSH_SetKeyingCompletionCbCtx(ssh, &kexCount);
|
|
|
|
/* Handshake completed in blocking mode; switch to non-blocking so the
|
|
* LS/rekey phase exercises the WS_WANT_READ/WS_WANT_WRITE early-return
|
|
* path in buffer_send/buffer_read. */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
if (nonBlock) {
|
|
tcp_set_nonblocking(&clientFd);
|
|
}
|
|
|
|
/* Low threshold makes the client cross its own highwater mid-SFTP and fire
|
|
* the default highwater callback (wsHighwater -> TriggerKeyExchange), so the
|
|
* client initiates a rekey. In blocking mode the buffer_read/buffer_send
|
|
* fixes drive it internally; in non-blocking mode the retry loop below
|
|
* advances it on WS_WANT_READ/WS_WANT_WRITE/WS_REKEYING. */
|
|
AssertIntEQ(wolfSSH_SetHighwater(ssh, 256), WS_SUCCESS);
|
|
|
|
ls = NULL;
|
|
for (i = 0; i < 3; i++) {
|
|
/* The retry loop only applies to non-blocking. In blocking mode the
|
|
* buffer_read/buffer_send fixes must handle the rekey transparently, so
|
|
* a single LS call returns the listing; gating on nonBlock keeps the
|
|
* blocking path from masking a regression that exposes WS_REKEYING. */
|
|
tries = 0;
|
|
do {
|
|
ls = wolfSSH_SFTP_LS(ssh, (char*)".");
|
|
err = wolfSSH_get_error(ssh);
|
|
/* tcp_select() waits for receive-readiness; on WS_WANT_WRITE it has
|
|
* no write event to wait on, so its 1s timeout is the intended
|
|
* (rare) fallback that yields the CPU instead of busy-spinning. */
|
|
if (nonBlock && ls == NULL && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING)) {
|
|
tcp_select(clientFd, 1);
|
|
}
|
|
tries++;
|
|
} while (nonBlock && ls == NULL && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING)
|
|
&& tries <= SFTP_REKEY_MAX_TRIES);
|
|
/* Fails fast (instead of hanging CI) if a regression keeps the LS stuck
|
|
* in a want/rekey state past the retry bound. The loop cap is one above
|
|
* the assert threshold so a legitimate success on the last allowed
|
|
* iteration is not misreported as a hang. */
|
|
AssertIntLE(tries, SFTP_REKEY_MAX_TRIES);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
}
|
|
|
|
/* A mid-SFTP rekey must have fired; otherwise the test silently stops
|
|
* exercising the buffer_send/buffer_read rekey paths it was written for. */
|
|
AssertIntGT(kexCount, 0);
|
|
|
|
tries = 0;
|
|
do {
|
|
argsCount = wolfSSH_shutdown(ssh);
|
|
err = wolfSSH_get_error(ssh);
|
|
if (argsCount != WS_SUCCESS && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING)) {
|
|
tcp_select(clientFd, 1);
|
|
}
|
|
tries++;
|
|
} while (argsCount != WS_SUCCESS && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING)
|
|
&& tries <= SFTP_REKEY_MAX_TRIES);
|
|
/* Fails fast if shutdown stays stuck in a want/rekey state past the bound,
|
|
* before the WS_REKEYING fixup below could otherwise mask it. The loop cap
|
|
* is one above the assert threshold to leave last-iteration headroom. */
|
|
AssertIntLE(tries, SFTP_REKEY_MAX_TRIES);
|
|
argsCount = AbsorbBenignReset(ssh, argsCount);
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
/* in cases where highwater mark is really small a re-key could happen */
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
k_sleep(Z_TIMEOUT_TICKS(100));
|
|
#endif
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
}
|
|
|
|
static void test_wolfSSH_SFTP_ReKey(void)
|
|
{
|
|
sftp_rekey_test(0);
|
|
}
|
|
|
|
static void test_wolfSSH_SFTP_ReKey_NonBlock(void)
|
|
{
|
|
sftp_rekey_test(1);
|
|
}
|
|
|
|
static void test_wolfSSH_SFTP_Confinement(void)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
THREAD_TYPE serThread;
|
|
WS_SFTPNAME* ls = NULL;
|
|
WS_SFTP_FILEATRB atr;
|
|
byte handle[WOLFSSH_MAX_HANDLE];
|
|
word32 handleSz;
|
|
int ret;
|
|
char curDir[] = ".";
|
|
char inJailDir[] = "confine_injail_dir";
|
|
/* The server is confined to its working directory ("."). Every "escape"
|
|
* targets an absolute, out-of-jail path. None of these are real system
|
|
* files, so a confinement bypass can never damage anything important. */
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
/* On hosted POSIX, stage real out-of-jail targets under a unique per-test
|
|
* temporary directory created with mkdtemp(). Fixed /tmp names race
|
|
* against parallel test jobs and against stale paths owned by another
|
|
* user, producing false failures unrelated to confinement; a private
|
|
* mkdtemp() directory we own avoids both. Because the fixtures exist on
|
|
* disk, a confinement bypass for read/stat/delete/rename would actually
|
|
* succeed and trip the assertions below - not merely fail with ENOENT.
|
|
* escMkdir/escDest are left absent so a leaked create also trips an
|
|
* assertion.
|
|
*
|
|
* The echoserver's jail is its working directory (".", which here is the
|
|
* test process's own cwd). If the temp root resolves inside that jail
|
|
* (e.g. the suite is run from within /tmp), the "escape" paths would fall
|
|
* in-jail and the rejection assertions would invert; that case is detected
|
|
* below and skipped. The in-jail positive case is covered by the repeated
|
|
* wolfSSH_SFTP_LS(ssh, ".") below, which must succeed after each reject.
|
|
* Blocking-mode LS handles any in-progress rekey transparently
|
|
* (buffer_read/buffer_send), so no manual rekey-drive helper is needed. */
|
|
char escRoot[] = "/tmp/wolfssh_confine_XXXXXX";
|
|
char escFile[WOLFSSH_MAX_FILENAME];
|
|
char escDir[WOLFSSH_MAX_FILENAME];
|
|
char escMkdir[WOLFSSH_MAX_FILENAME];
|
|
char escDest[WOLFSSH_MAX_FILENAME];
|
|
char jailCwd[WOLFSSH_MAX_FILENAME];
|
|
/* a relative ".." traversal that resolves to the same real out-of-jail
|
|
* file as escFile, exercising the post-RealPath containment check on the
|
|
* relative-escape path (not just absolute paths) */
|
|
char escRel[WOLFSSH_MAX_FILENAME];
|
|
/* a real sibling directory whose name is the jail's name with a distinctive
|
|
* suffix appended directly (no separator); its resolved path matches the
|
|
* jail for the full prefix length but the next byte is not a delimiter, so
|
|
* only GetAndCleanPath's boundary check (not a plain prefix compare) rejects
|
|
* it. The suffix is test-specific so it will not collide with real user
|
|
* directories, and it is only created/removed when this run actually staged
|
|
* it. Empty when the cwd could not be resolved, the name would truncate, or
|
|
* such a directory already exists (the sub-test is then skipped rather than
|
|
* touching unrelated data). */
|
|
char escSibling[WOLFSSH_MAX_FILENAME];
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* an in-jail symlink pointing at the out-of-jail temp root, and a path
|
|
* that traverses it; both must be rejected even though they resolve to an
|
|
* in-jail string, since wolfSSH_RealPath does not follow links. Guarded by
|
|
* WOLFSSH_HAVE_SYMLINK to match the server-side check's feature gate: on
|
|
* POSIX builds that compile the check out (e.g. WOLFSSH_USER_FILESYSTEM)
|
|
* the link would be followed as designed, so these assertions must not
|
|
* run. */
|
|
char escSymlink[] = "confine_symlink";
|
|
char escSymThru[WOLFSSH_MAX_FILENAME];
|
|
#endif
|
|
WFILE* fp = NULL;
|
|
int snLen;
|
|
#else
|
|
/* Zephyr (and Windows, which does not run this via "make check") lack the
|
|
* getcwd()/fopen() wrappers to stage out-of-jail files, so fall back to
|
|
* non-existent out-of-jail paths. A leaked MKDIR still trips its
|
|
* assertion; read/stat/delete bypasses are not detectable here. */
|
|
char escFile[] = "/wolfssh_confine_test_file";
|
|
char escDir[] = "/wolfssh_confine_test_dir";
|
|
char escMkdir[] = "/wolfssh_confine_test_mkdir";
|
|
char escDest[] = "/wolfssh_confine_test_renamed";
|
|
#endif
|
|
|
|
/* best effort removal of anything a previous aborted run may have left */
|
|
WRMDIR(NULL, inJailDir);
|
|
#if defined(WOLFSSH_ZEPHYR) || defined(USE_WINDOWS_API)
|
|
WREMOVE(NULL, escFile);
|
|
WRMDIR(NULL, escDir);
|
|
WRMDIR(NULL, escMkdir);
|
|
WREMOVE(NULL, escDest);
|
|
#else
|
|
/* Create a private, unique temp directory to hold the out-of-jail
|
|
* fixtures, then derive the individual escape paths from it. */
|
|
AssertNotNull(mkdtemp(escRoot));
|
|
|
|
/* If the temp root resolves inside the jail (the test process's cwd),
|
|
* the "escape" paths would actually be in-jail and the rejection
|
|
* assertions would invert; skip the staged-fixture checks in that
|
|
* unusual case rather than report a bogus confinement failure. */
|
|
WMEMSET(jailCwd, 0, sizeof(jailCwd));
|
|
escSibling[0] = '\0';
|
|
if (WGETCWD(NULL, jailCwd, sizeof(jailCwd) - 1) != NULL) {
|
|
size_t jailLen = WSTRLEN(jailCwd);
|
|
if (WSTRLEN(escRoot) >= jailLen &&
|
|
WSTRNCMP(escRoot, jailCwd, jailLen) == 0) {
|
|
WRMDIR(NULL, escRoot);
|
|
return;
|
|
}
|
|
/* "<cwd>_wolfssh_confine_sibling" - a sibling of the jail sharing its
|
|
* name as a string prefix, with a distinctive test-specific suffix so
|
|
* it will not match a real user directory. The first byte past the
|
|
* jail prefix is '_' (not a delimiter), so the boundary check rejects
|
|
* it. If the name would truncate, leave escSibling empty so the
|
|
* boundary-check case is skipped rather than staged at a wrong path. */
|
|
snLen = WSNPRINTF(escSibling, sizeof(escSibling),
|
|
"%s_wolfssh_confine_sibling", jailCwd);
|
|
if (snLen < 0 || (size_t)snLen >= sizeof(escSibling)) {
|
|
escSibling[0] = '\0';
|
|
}
|
|
}
|
|
|
|
WSNPRINTF(escFile, sizeof(escFile), "%s/real_file", escRoot);
|
|
WSNPRINTF(escDir, sizeof(escDir), "%s/real_dir", escRoot);
|
|
WSNPRINTF(escMkdir, sizeof(escMkdir), "%s/mkdir", escRoot);
|
|
WSNPRINTF(escDest, sizeof(escDest), "%s/renamed", escRoot);
|
|
/* climb to filesystem root with a generous ".." count (RealPath clamps the
|
|
* excess at root) then re-descend to escFile, so this relative path
|
|
* resolves to the very same out-of-jail file the absolute escFile does */
|
|
snLen = WSNPRINTF(escRel, sizeof(escRel),
|
|
"../../../../../../../../../../../../../../../../%s", escFile + 1);
|
|
AssertIntGE(snLen, 0);
|
|
AssertIntLT(snLen, (int)sizeof(escRel));
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* a path that traverses the in-jail symlink out to the staged real file */
|
|
WSNPRINTF(escSymThru, sizeof(escSymThru), "%s/real_file", escSymlink);
|
|
#endif
|
|
|
|
/* stage the real out-of-jail file and directory */
|
|
AssertIntEQ(WFOPEN(NULL, &fp, escFile, "wb"), 0);
|
|
AssertNotNull(fp);
|
|
WFCLOSE(NULL, fp);
|
|
AssertIntEQ(WMKDIR(NULL, escDir, 0755), 0);
|
|
|
|
/* stage the sibling so a boundary-check regression would actually
|
|
* enumerate it (rather than fail with ENOENT). Never remove a pre-existing
|
|
* directory: only create it when absent, and if creation fails (e.g. it
|
|
* already exists, possibly user data despite the distinctive name), clear
|
|
* escSibling so the sub-test is skipped and cleanup leaves it untouched.
|
|
* escSibling is thus non-empty only when this run created the directory. */
|
|
if (escSibling[0] != '\0') {
|
|
if (WMKDIR(NULL, escSibling, 0755) != 0) {
|
|
escSibling[0] = '\0';
|
|
}
|
|
}
|
|
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* stage an in-jail symlink pointing at the out-of-jail temp root */
|
|
WREMOVE(NULL, escSymlink);
|
|
AssertIntEQ(symlink(escRoot, escSymlink), 0);
|
|
#endif
|
|
#endif
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-D"; /* confine to the working directory */
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* The client API maps PERMISSION and FAILURE both to WS_FATAL_ERROR;
|
|
* assert != WS_SUCCESS and verify the session stays alive afterward. */
|
|
|
|
/* Remove: out-of-jail absolute path -> rejected, session survives */
|
|
ret = wolfSSH_SFTP_Remove(ssh, escFile);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* Remove: relative ".." traversal resolving to the same real out-of-jail
|
|
* file -> rejected by the post-RealPath containment check, session
|
|
* survives. escFile still exists afterward (a bypass would have deleted
|
|
* it, failing the absolute-path assertions on a re-run). escRel/fp are
|
|
* staged only on hosted POSIX (mkdtemp/fopen), so this case is POSIX-only;
|
|
* on Zephyr/Windows the absolute-path rejection above already covers the
|
|
* Remove sink. */
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
ret = wolfSSH_SFTP_Remove(ssh, escRel);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
AssertIntEQ(WFOPEN(NULL, &fp, escFile, "rb"), 0);
|
|
AssertNotNull(fp);
|
|
WFCLOSE(NULL, fp);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
#endif
|
|
|
|
/* RMDIR: out-of-jail path -> rejected, session survives */
|
|
ret = wolfSSH_SFTP_RMDIR(ssh, escDir);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* MKDIR: out-of-jail path -> rejected, session survives */
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
ret = wolfSSH_SFTP_MKDIR(ssh, escMkdir, &atr);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* Open: out-of-jail path -> rejected, session survives */
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
ret = wolfSSH_SFTP_Open(ssh, escFile, WOLFSSH_FXF_READ, NULL,
|
|
handle, &handleSz);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* LS (OpenDir): out-of-jail path -> rejected, session survives */
|
|
ls = wolfSSH_SFTP_LS(ssh, escDir);
|
|
AssertNull(ls);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
/* LS (OpenDir) on the "<jail>_wolfssh_confine_sibling" sibling: its resolved
|
|
* path shares the jail prefix exactly but the next byte is '_' (not a
|
|
* delimiter), so it must be rejected by the boundary check even though a
|
|
* plain prefix compare would accept it. The dir really exists, so a
|
|
* regression would return a non-NULL listing. */
|
|
if (escSibling[0] != '\0') {
|
|
ls = wolfSSH_SFTP_LS(ssh, escSibling);
|
|
AssertNull(ls);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Rename: out-of-jail path -> rejected, session survives */
|
|
ret = wolfSSH_SFTP_Rename(ssh, escFile, escDest);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* STAT: out-of-jail path -> rejected, session survives */
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
ret = wolfSSH_SFTP_STAT(ssh, escFile, &atr);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* LSTAT: out-of-jail path -> rejected, session survives */
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
ret = wolfSSH_SFTP_LSTAT(ssh, escFile, &atr);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* SetSTAT: out-of-jail path -> rejected, session survives */
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
ret = wolfSSH_SFTP_SetSTAT(ssh, escFile, &atr);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API) && \
|
|
defined(WOLFSSH_HAVE_SYMLINK)
|
|
/* Symlink escape: an in-jail symlink to the out-of-jail tree resolves to
|
|
* an in-jail path string, so the prefix check alone would pass; the
|
|
* per-component link check must reject both listing the link itself and
|
|
* opening a file through it. Without the fix these would follow the link
|
|
* and succeed, escaping the jail. Guarded to match the POSIX-only staging
|
|
* above (mkdtemp/symlink) and WOLFSSH_HAVE_SYMLINK so it only runs where
|
|
* both the fixtures and the server-side link check exist. */
|
|
ls = wolfSSH_SFTP_LS(ssh, escSymlink);
|
|
AssertNull(ls);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
ret = wolfSSH_SFTP_Open(ssh, escSymThru, WOLFSSH_FXF_READ, NULL,
|
|
handle, &handleSz);
|
|
AssertIntNE(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
#endif
|
|
|
|
/* Positive case: a relative write op that resolves inside the jail must be
|
|
* allowed. This guards the GetAndCleanPath prefix-compare allow path (and
|
|
* the s[dpLen] boundary check) against an over-restrictive regression that
|
|
* would only be caught by the broader CI shell scripts otherwise. MKDIR
|
|
* the in-jail name, assert success, then RMDIR it back to a clean state. */
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
ret = wolfSSH_SFTP_MKDIR(ssh, inJailDir, &atr);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
ret = wolfSSH_SFTP_RMDIR(ssh, inJailDir);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* Drain any pending rekey before shutdown. */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING)
|
|
wolfSSH_worker(ssh, NULL);
|
|
|
|
ret = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (ret == WS_REKEYING) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
k_sleep(Z_TIMEOUT_TICKS(100));
|
|
#endif
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
|
|
/* remove staged targets; escMkdir/escDest only exist if confinement
|
|
* leaked, and inJailDir only if the positive-case RMDIR did not run, so
|
|
* their removal is best effort */
|
|
WREMOVE(NULL, escFile);
|
|
WRMDIR(NULL, escDir);
|
|
WRMDIR(NULL, escMkdir);
|
|
WREMOVE(NULL, escDest);
|
|
WRMDIR(NULL, inJailDir);
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
WREMOVE(NULL, escSymlink);
|
|
#endif
|
|
WRMDIR(NULL, escRoot);
|
|
/* escSibling is non-empty only if this run created it (see staging above),
|
|
* so this never removes a pre-existing directory belonging to the user */
|
|
if (escSibling[0] != '\0') {
|
|
WRMDIR(NULL, escSibling);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/* A session given only a start path is NOT confined to it: the wolfsshd
|
|
* arrangement, where the OS bounds access instead. The echoserver runs
|
|
* without -D, so out-of-tree paths must be reachable - the inverse of
|
|
* test_wolfSSH_SFTP_Confinement, proving GetAndCleanPath enforces the
|
|
* confinement root and not the start path. */
|
|
static void test_wolfSSH_SFTP_StartPathNotConfined(void)
|
|
{
|
|
/* Staging the out-of-tree fixtures needs mkdtemp()/fopen(), as in
|
|
* test_wolfSSH_SFTP_Confinement, so this is hosted POSIX only. */
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
THREAD_TYPE serThread;
|
|
WS_SFTPNAME* ls = NULL;
|
|
WS_SFTP_FILEATRB atr;
|
|
int ret;
|
|
char curDir[] = ".";
|
|
char outRoot[] = "/tmp/wolfssh_startpath_XXXXXX";
|
|
char outFile[WOLFSSH_MAX_FILENAME];
|
|
char outDir[WOLFSSH_MAX_FILENAME];
|
|
char startCwd[WOLFSSH_MAX_FILENAME];
|
|
WFILE* fp = NULL;
|
|
|
|
AssertNotNull(mkdtemp(outRoot));
|
|
|
|
/* A temp root inside the start directory (the test process's cwd) would
|
|
* make the paths below in-tree, proving nothing; skip instead. */
|
|
WMEMSET(startCwd, 0, sizeof(startCwd));
|
|
if (WGETCWD(NULL, startCwd, sizeof(startCwd) - 1) != NULL) {
|
|
size_t startLen = WSTRLEN(startCwd);
|
|
if (WSTRLEN(outRoot) >= startLen &&
|
|
WSTRNCMP(outRoot, startCwd, startLen) == 0) {
|
|
WRMDIR(NULL, outRoot);
|
|
return;
|
|
}
|
|
}
|
|
|
|
WSNPRINTF(outFile, sizeof(outFile), "%s/real_file", outRoot);
|
|
WSNPRINTF(outDir, sizeof(outDir), "%s/real_dir", outRoot);
|
|
AssertIntEQ(WFOPEN(NULL, &fp, outFile, "wb"), 0);
|
|
AssertNotNull(fp);
|
|
WFCLOSE(NULL, fp);
|
|
AssertIntEQ(WMKDIR(NULL, outDir, 0755), 0);
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p"; /* no -D, so the session is unconfined */
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* the session starts in the working directory */
|
|
ls = wolfSSH_SFTP_LS(ssh, curDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
/* and can still reach outside it: listing and stat both succeed */
|
|
ls = wolfSSH_SFTP_LS(ssh, outDir);
|
|
AssertNotNull(ls);
|
|
wolfSSH_SFTPNAME_list_free(ls);
|
|
ls = NULL;
|
|
|
|
WMEMSET(&atr, 0, sizeof(atr));
|
|
AssertIntEQ(wolfSSH_SFTP_STAT(ssh, outFile, &atr), WS_SUCCESS);
|
|
|
|
/* Drain any pending rekey before shutdown. */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING)
|
|
wolfSSH_worker(ssh, NULL);
|
|
|
|
ret = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (ret == WS_REKEYING) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
|
|
WREMOVE(NULL, outFile);
|
|
WRMDIR(NULL, outDir);
|
|
WRMDIR(NULL, outRoot);
|
|
#endif /* !WOLFSSH_ZEPHYR && !USE_WINDOWS_API */
|
|
}
|
|
|
|
|
|
/* The start path and the confinement root are stored independently: setting
|
|
* one must not disturb the other, and the default path does not confine. */
|
|
static void test_wolfSSH_SFTP_SetConfinePath(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
char longPath[WOLFSSH_MAX_FILENAME + 4];
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
AssertIntEQ(wolfSSH_SFTP_SetConfinePath(NULL, "/"), WS_BAD_ARGUMENT);
|
|
|
|
/* a root that does not fit the working buffer is rejected up front and
|
|
* leaves the session unconfined */
|
|
WMEMSET(longPath, 'a', sizeof(longPath));
|
|
longPath[0] = '/';
|
|
longPath[WOLFSSH_MAX_FILENAME + 1] = '\0'; /* length == MAX_FILENAME + 1 */
|
|
AssertIntEQ(wolfSSH_SFTP_SetConfinePath(ssh, longPath), WS_BUFFER_E);
|
|
AssertNull(ssh->sftpConfinePath);
|
|
AssertNull(ssh->sftpDefaultPath);
|
|
|
|
/* a NULL path leaves both settings alone */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, NULL), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_SFTP_SetConfinePath(ssh, NULL), WS_SUCCESS);
|
|
AssertNull(ssh->sftpDefaultPath);
|
|
AssertNull(ssh->sftpConfinePath);
|
|
|
|
/* a start path on its own does not confine the session */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, "/tmp/../tmp/start"),
|
|
WS_SUCCESS);
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/tmp/start");
|
|
AssertNull(ssh->sftpConfinePath);
|
|
|
|
/* a confinement root on its own does not move the start path */
|
|
AssertIntEQ(wolfSSH_SFTP_SetConfinePath(ssh, "/tmp/./jail"), WS_SUCCESS);
|
|
AssertStrEQ(ssh->sftpConfinePath, "/tmp/jail");
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/tmp/start");
|
|
|
|
/* each is replaceable without touching the other */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, "/tmp/jail/sub"), WS_SUCCESS);
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/tmp/jail/sub");
|
|
AssertStrEQ(ssh->sftpConfinePath, "/tmp/jail");
|
|
|
|
AssertIntEQ(wolfSSH_SFTP_SetConfinePath(ssh, "/var/jail2"), WS_SUCCESS);
|
|
AssertStrEQ(ssh->sftpConfinePath, "/var/jail2");
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/tmp/jail/sub");
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
/* Direct unit coverage for wolfSSH_SFTP_SetDefaultPath, exercising the new
|
|
* canonicalization and error branches that test_wolfSSH_SFTP_Confinement only
|
|
* reaches indirectly (it always passes an already-absolute realpath):
|
|
* NULL ssh, the too-long-path guard, NULL path (no change), absolute-path
|
|
* canonicalization, the repeated-call free path, and relative-path resolution
|
|
* against the canonicalized cwd. */
|
|
static void test_wolfSSH_SFTP_SetDefaultPath(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
char longPath[WOLFSSH_MAX_FILENAME + 4];
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
char cwdBuf[WOLFSSH_MAX_FILENAME];
|
|
char cwdReal[WOLFSSH_MAX_FILENAME];
|
|
char expect[WOLFSSH_MAX_FILENAME];
|
|
char rel[] = "sdp_rel_seg";
|
|
#endif
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
/* NULL ssh is rejected */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(NULL, "/"), WS_BAD_ARGUMENT);
|
|
|
|
/* NULL path leaves the (still unset) default path unchanged */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, NULL), WS_SUCCESS);
|
|
AssertNull(ssh->sftpDefaultPath);
|
|
|
|
/* A path that does not fit the working buffer is rejected up front and
|
|
* does not store anything */
|
|
WMEMSET(longPath, 'a', sizeof(longPath));
|
|
longPath[0] = '/';
|
|
longPath[WOLFSSH_MAX_FILENAME + 1] = '\0'; /* length == MAX_FILENAME + 1 */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, longPath), WS_BUFFER_E);
|
|
AssertNull(ssh->sftpDefaultPath);
|
|
AssertNull(ssh->sftpConfinePath);
|
|
|
|
/* An absolute path is stored in lexically canonical form as the start
|
|
* path, and does not confine the session */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, "/tmp/../tmp/sdp"),
|
|
WS_SUCCESS);
|
|
AssertNotNull(ssh->sftpDefaultPath);
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/tmp/sdp");
|
|
AssertNull(ssh->sftpConfinePath);
|
|
|
|
/* A repeated call frees the previous path (no leak) and stores the new
|
|
* one - the wolfsshd "/" then home-dir sequence */
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, "/var/sdp2"), WS_SUCCESS);
|
|
AssertNotNull(ssh->sftpDefaultPath);
|
|
AssertStrEQ(ssh->sftpDefaultPath, "/var/sdp2");
|
|
|
|
#if !defined(WOLFSSH_ZEPHYR) && !defined(USE_WINDOWS_API)
|
|
/* A relative path is resolved against the canonicalized cwd, so the stored
|
|
* path is absolute and matches cwd + "/seg" rather than a lexical "/seg" -
|
|
* confirming the relative branch ran. The expected value is built with
|
|
* the same two RealPath passes the implementation uses. */
|
|
AssertNotNull(WGETCWD(NULL, cwdBuf, sizeof(cwdBuf) - 1));
|
|
AssertIntEQ(wolfSSH_RealPath(NULL, cwdBuf, cwdReal, sizeof(cwdReal)),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_RealPath(cwdReal, rel, expect, sizeof(expect)),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, "sdp_rel_seg"), WS_SUCCESS);
|
|
AssertNotNull(ssh->sftpDefaultPath);
|
|
AssertStrEQ(ssh->sftpDefaultPath, expect);
|
|
#endif
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
/* Saved reget/reput names are NUL terminated in char[WOLFSSH_MAX_FILENAME]
|
|
* fields, so a name of exactly WOLFSSH_MAX_FILENAME must be rejected. */
|
|
static void test_wolfSSH_SFTP_SaveOfst(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
char maxName[WOLFSSH_MAX_FILENAME + 1];
|
|
char fitName[WOLFSSH_MAX_FILENAME];
|
|
word32 ofst[2];
|
|
word32 got[2];
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
/* name of length WOLFSSH_MAX_FILENAME, leaving no room for a terminator */
|
|
WMEMSET(maxName, 'a', WOLFSSH_MAX_FILENAME);
|
|
maxName[WOLFSSH_MAX_FILENAME] = '\0';
|
|
|
|
/* longest name that still fits the field with its terminator */
|
|
WMEMSET(fitName, 'b', WOLFSSH_MAX_FILENAME - 1);
|
|
fitName[WOLFSSH_MAX_FILENAME - 1] = '\0';
|
|
|
|
ofst[0] = 0x2000;
|
|
ofst[1] = 1;
|
|
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(NULL, fitName, fitName, ofst),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, maxName, fitName, ofst),
|
|
WS_BUFFER_E);
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, fitName, maxName, ofst),
|
|
WS_BUFFER_E);
|
|
|
|
/* the rejected calls stored nothing */
|
|
got[0] = 1;
|
|
got[1] = 1;
|
|
AssertIntEQ(wolfSSH_SFTP_GetOfst(ssh, maxName, fitName, got), WS_SUCCESS);
|
|
AssertIntEQ(got[0], 0);
|
|
AssertIntEQ(got[1], 0);
|
|
AssertIntEQ(wolfSSH_SFTP_GetOfst(ssh, fitName, maxName, got), WS_SUCCESS);
|
|
AssertIntEQ(got[0], 0);
|
|
AssertIntEQ(got[1], 0);
|
|
|
|
/* a name that fits is saved and read back */
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, fitName, fitName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_SFTP_GetOfst(ssh, fitName, fitName, got), WS_SUCCESS);
|
|
AssertIntEQ(got[0], (int)ofst[0]);
|
|
AssertIntEQ(got[1], (int)ofst[1]);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM) && \
|
|
!defined(WOLFSSH_ZEPHYR)
|
|
|
|
#define SFTP_PUT_RESUME_SZ 1024
|
|
#define SFTP_PUT_RESUME_OFST 256
|
|
|
|
/* Fills buf with the source file's byte pattern. */
|
|
static void sftpPutFillPattern(byte* buf, word32 sz)
|
|
{
|
|
word32 i;
|
|
|
|
for (i = 0; i < sz; i++) {
|
|
buf[i] = (byte)(i * 7 + 1);
|
|
}
|
|
}
|
|
|
|
|
|
/* Writes sz bytes of buf to the local file name. Returns 0 on success. */
|
|
static int sftpPutWriteFile(const char* name, const byte* buf, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
int ret = 0;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "wb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFWRITE(NULL, buf, 1, sz, fp) != sz)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Returns 0 if name holds exactly sz bytes equal to expect. Asking for one
|
|
* byte more than expected also catches a file left too long. */
|
|
static int sftpPutFileMatches(const char* name, const byte* expect, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
byte got[SFTP_PUT_RESUME_SZ + 1];
|
|
int ret = 0;
|
|
|
|
if (sz >= sizeof(got))
|
|
return -1;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "rb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFREAD(NULL, got, 1, sz + 1, fp) != sz)
|
|
ret = -1;
|
|
|
|
if (ret == 0 && WMEMCMP(got, expect, sz) != 0)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Leaves sz bytes of buf in the remote file name, the way an interrupted
|
|
* upload would. Returns WS_SUCCESS on success. */
|
|
static int sftpPutStageRemote(WOLFSSH* ssh, char* name, byte* buf, word32 sz)
|
|
{
|
|
byte handle[WOLFSSH_MAX_HANDLE];
|
|
word32 handleSz;
|
|
word32 wrote = 0;
|
|
word32 ofst[2];
|
|
int ret = WS_FATAL_ERROR;
|
|
int tries;
|
|
int sent;
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
|
|
handleSz = WOLFSSH_MAX_HANDLE;
|
|
ret = wolfSSH_SFTP_Open(ssh, name,
|
|
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
|
|
NULL, handle, &handleSz);
|
|
if (ret == WS_SUCCESS ||
|
|
!sftp_error_keeps_state(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
if (ret != WS_SUCCESS) {
|
|
return ret;
|
|
}
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES && wrote < sz; tries++) {
|
|
ofst[0] = wrote;
|
|
ofst[1] = 0;
|
|
sent = wolfSSH_SFTP_SendWritePacket(ssh, handle, handleSz, ofst,
|
|
buf + wrote, sz - wrote);
|
|
if (sent > 0) {
|
|
wrote += (word32)sent;
|
|
continue;
|
|
}
|
|
if (!sftp_error_keeps_state(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = sftp_retry_close(ssh, handle, handleSz);
|
|
if (ret == WS_SUCCESS && wrote != sz) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Drives one wolfSSH_SFTP_Put() to a terminal result. */
|
|
static int sftpPutToCompletion(WOLFSSH* ssh, char* from, char* to, byte resume)
|
|
{
|
|
int ret = WS_FATAL_ERROR;
|
|
int tries;
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
|
|
ret = wolfSSH_SFTP_Put(ssh, from, to, resume, NULL);
|
|
if (ret == WS_SUCCESS ||
|
|
!sftp_error_is_notice(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Drives one wolfSSH_SFTP_Get() to a terminal result. */
|
|
static int sftpGetToCompletion(WOLFSSH* ssh, char* from, char* to, byte resume)
|
|
{
|
|
int ret = WS_FATAL_ERROR;
|
|
int tries;
|
|
|
|
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
|
|
ret = wolfSSH_SFTP_Get(ssh, from, to, resume, NULL);
|
|
if (ret == WS_SUCCESS ||
|
|
!sftp_error_is_notice(wolfSSH_get_error(ssh))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
|
|
|
|
|
|
/* A resumed put must keep the bytes already at the destination, and must
|
|
* only resume onto a destination whose size matches the saved offset. The
|
|
* echoserver shares this process, so the "remote" file is read locally. */
|
|
static void test_wolfSSH_SFTP_PutResume(void)
|
|
{
|
|
/* staging the local source file needs the hosted fopen()/getcwd() wrappers */
|
|
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM) && \
|
|
!defined(WOLFSSH_ZEPHYR)
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
byte src[SFTP_PUT_RESUME_SZ];
|
|
byte stale[SFTP_PUT_RESUME_SZ + SFTP_PUT_RESUME_OFST];
|
|
byte expect[SFTP_PUT_RESUME_SZ];
|
|
word32 ofst[2];
|
|
char srcName[] = "wolfssh_11659_src.tmp";
|
|
char dstName[] = "wolfssh_11659_dst.tmp";
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
sftpPutFillPattern(src, (word32)sizeof(src));
|
|
WMEMSET(stale, 0xFF, sizeof(stale));
|
|
AssertIntEQ(sftpPutWriteFile(srcName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the saved offset matches the destination, so the upload picks up where
|
|
* it left off. Staging a prefix unlike the source, and expecting it back
|
|
* untouched, is what separates a resume from a full re-upload. */
|
|
(void)sftp_retry_remove(ssh, dstName); /* normally absent */
|
|
AssertIntEQ(sftpPutStageRemote(ssh, dstName, stale, SFTP_PUT_RESUME_OFST),
|
|
WS_SUCCESS);
|
|
WMEMCPY(expect, stale, SFTP_PUT_RESUME_OFST);
|
|
WMEMCPY(expect + SFTP_PUT_RESUME_OFST, src + SFTP_PUT_RESUME_OFST,
|
|
sizeof(expect) - SFTP_PUT_RESUME_OFST);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, expect, (word32)sizeof(expect)), 0);
|
|
|
|
/* the destination is gone, so the whole file is sent again */
|
|
AssertIntEQ(sftp_retry_remove(ssh, dstName), WS_SUCCESS);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the destination is longer than the saved offset, so the whole file is
|
|
* sent again; staging bytes unlike the source makes a resume here show
|
|
* up in the result */
|
|
AssertIntEQ(sftp_retry_remove(ssh, dstName), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutStageRemote(ssh, dstName, stale, SFTP_PUT_RESUME_OFST),
|
|
WS_SUCCESS);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST / 2;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* a plain put still truncates a longer destination */
|
|
AssertIntEQ(sftp_retry_remove(ssh, dstName), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutStageRemote(ssh, dstName, stale, (word32)sizeof(stale)),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutToCompletion(ssh, srcName, dstName, 0), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the source has nothing left past the saved offset, so the whole file
|
|
* is sent again. This case shortens the source, so it runs last. */
|
|
AssertIntEQ(sftp_retry_remove(ssh, dstName), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutStageRemote(ssh, dstName, stale, SFTP_PUT_RESUME_OFST),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutWriteFile(srcName, src, SFTP_PUT_RESUME_OFST / 2), 0);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpPutToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, SFTP_PUT_RESUME_OFST / 2), 0);
|
|
|
|
AssertIntEQ(sftp_retry_remove(ssh, dstName), WS_SUCCESS);
|
|
WREMOVE(NULL, srcName);
|
|
|
|
/* take care of re-keying state before shutdown call */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING) {
|
|
wolfSSH_worker(ssh, NULL);
|
|
}
|
|
|
|
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
|
|
}
|
|
|
|
|
|
/* A resumed get must keep the bytes already at the local destination, and
|
|
* must only resume onto one whose size matches the saved offset. The
|
|
* echoserver shares this process, so the "remote" file is local. */
|
|
static void test_wolfSSH_SFTP_GetResume(void)
|
|
{
|
|
/* staging both files needs the hosted fopen()/getcwd() wrappers */
|
|
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM) && \
|
|
!defined(WOLFSSH_ZEPHYR)
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
byte src[SFTP_PUT_RESUME_SZ];
|
|
byte stale[SFTP_PUT_RESUME_SZ + SFTP_PUT_RESUME_OFST];
|
|
byte expect[SFTP_PUT_RESUME_SZ];
|
|
word32 ofst[2];
|
|
char srcName[] = "wolfssh_12547_src.tmp";
|
|
char dstName[] = "wolfssh_12547_dst.tmp";
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
sftp_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
sftpPutFillPattern(src, (word32)sizeof(src));
|
|
WMEMSET(stale, 0xFF, sizeof(stale));
|
|
AssertIntEQ(sftpPutWriteFile(srcName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the saved offset matches the destination, so the download picks up
|
|
* where it left off. Staging a prefix unlike the source, and expecting it
|
|
* back untouched, is what separates a resume from a full re-download. */
|
|
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST), 0);
|
|
WMEMCPY(expect, stale, SFTP_PUT_RESUME_OFST);
|
|
WMEMCPY(expect + SFTP_PUT_RESUME_OFST, src + SFTP_PUT_RESUME_OFST,
|
|
sizeof(expect) - SFTP_PUT_RESUME_OFST);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, expect, (word32)sizeof(expect)),
|
|
0);
|
|
|
|
/* the destination is gone, so the whole file is fetched again */
|
|
WREMOVE(NULL, dstName);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the destination is shorter than the saved offset, so the whole file is
|
|
* fetched again */
|
|
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST / 2), 0);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the destination is longer than the saved offset, so the whole file is
|
|
* fetched again */
|
|
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST * 2), 0);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* a plain get still truncates a longer destination */
|
|
AssertIntEQ(sftpPutWriteFile(dstName, stale, (word32)sizeof(stale)), 0);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 0), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
|
|
|
|
/* the source has nothing left past the saved offset, so the whole file
|
|
* is fetched again. This case shortens the source, so it runs last. */
|
|
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST), 0);
|
|
AssertIntEQ(sftpPutWriteFile(srcName, src, SFTP_PUT_RESUME_OFST / 2), 0);
|
|
ofst[0] = SFTP_PUT_RESUME_OFST;
|
|
ofst[1] = 0;
|
|
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
|
|
WS_SUCCESS);
|
|
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
|
|
AssertIntEQ(sftpPutFileMatches(dstName, src, SFTP_PUT_RESUME_OFST / 2), 0);
|
|
|
|
WREMOVE(NULL, dstName);
|
|
WREMOVE(NULL, srcName);
|
|
|
|
/* take care of re-keying state before shutdown call */
|
|
while (wolfSSH_get_error(ssh) == WS_REKEYING) {
|
|
wolfSSH_worker(ssh, NULL);
|
|
}
|
|
|
|
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
|
|
}
|
|
|
|
|
|
#else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
|
|
static void test_wolfSSH_SFTP_SendReadPacket(void) { ; }
|
|
static void test_wolfSSH_SFTP_PartialSend(void) { ; }
|
|
static void test_wolfSSH_SFTP_ReKey(void) { ; }
|
|
static void test_wolfSSH_SFTP_ReKey_NonBlock(void) { ; }
|
|
static void test_wolfSSH_SFTP_Confinement(void) { ; }
|
|
static void test_wolfSSH_SFTP_StartPathNotConfined(void) { ; }
|
|
static void test_wolfSSH_SFTP_SetConfinePath(void) { ; }
|
|
static void test_wolfSSH_SFTP_SetDefaultPath(void) { ; }
|
|
static void test_wolfSSH_SFTP_SaveOfst(void) { ; }
|
|
static void test_wolfSSH_SFTP_PutResume(void) { ; }
|
|
static void test_wolfSSH_SFTP_GetResume(void) { ; }
|
|
#endif /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
|
|
|
|
|
|
#if defined(WOLFSSH_SCP) && !defined(NO_WOLFSSH_CLIENT) && \
|
|
!defined(SINGLE_THREADED) && !defined(NO_FILESYSTEM) && \
|
|
!defined(WOLFSSH_SCP_USER_CALLBACKS) && !defined(WOLFSSH_ZEPHYR)
|
|
|
|
/* Upper bound on non-blocking retry iterations. A legitimate transfer across a
|
|
* forced rekey completes in well under this; the bound keeps a regression from
|
|
* hanging CI by tripping the AssertIntLE below instead. */
|
|
#define SCP_REKEY_MAX_TRIES 100
|
|
|
|
/* Payload larger than the forced highwater so the transfer straddles it. */
|
|
#define SCP_REKEY_FILE_SZ 2048
|
|
|
|
static byte scpUserPassword[256];
|
|
|
|
static int scpUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
|
|
{
|
|
int ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
|
|
|
|
if (authType == WOLFSSH_USERAUTH_PASSWORD) {
|
|
const char* password = (const char*)ctx;
|
|
word32 passwordSz;
|
|
|
|
if (password != NULL) {
|
|
passwordSz = (word32)WSTRLEN(password);
|
|
if (passwordSz > (word32)sizeof(scpUserPassword))
|
|
passwordSz = (word32)sizeof(scpUserPassword);
|
|
WMEMCPY(scpUserPassword, password, passwordSz);
|
|
authData->sf.password.password = scpUserPassword;
|
|
authData->sf.password.passwordSz = passwordSz;
|
|
ret = WOLFSSH_USERAUTH_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int scpAcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz,
|
|
void* ctx)
|
|
{
|
|
(void)pubKey;
|
|
(void)pubKeySz;
|
|
(void)ctx;
|
|
return 0;
|
|
}
|
|
|
|
/* Counts keying completions (initial handshake and each rekey) so the test can
|
|
* assert a mid-transfer rekey actually fired. ctx points to an int counter. */
|
|
static void scpKeyingCompleteCb(void* ctx)
|
|
{
|
|
if (ctx != NULL)
|
|
(*(int*)ctx)++;
|
|
}
|
|
|
|
/* Writes sz bytes of buf to name. Returns 0 on success. */
|
|
static int scpWriteTestFile(const char* name, const byte* buf, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
int ret = 0;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "wb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFWRITE(NULL, buf, 1, sz, fp) != sz)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
/* Returns 0 if the first sz bytes of name match expect. */
|
|
static int scpFilesMatch(const char* name, const byte* expect, word32 sz)
|
|
{
|
|
WFILE* fp = NULL;
|
|
byte got[SCP_REKEY_FILE_SZ];
|
|
int ret = 0;
|
|
|
|
if (sz > sizeof(got))
|
|
return -1;
|
|
|
|
if (WFOPEN(NULL, &fp, name, "rb") != 0 || fp == NULL)
|
|
return -1;
|
|
|
|
if (WFREAD(NULL, got, 1, sz, fp) != sz)
|
|
ret = -1;
|
|
|
|
if (ret == 0 && XMEMCMP(got, expect, sz) != 0)
|
|
ret = -1;
|
|
|
|
WFCLOSE(NULL, fp);
|
|
return ret;
|
|
}
|
|
|
|
/* Connects an SCP client to port, completes the SSH handshake and opens the
|
|
* exec channel carrying cmd, leaving ssh ready for wolfSSH_SCP_to/from. Doing
|
|
* the handshake here (rather than inside the transfer call) lets the caller set
|
|
* a low highwater before the data phase so a rekey fires mid-transfer.
|
|
*/
|
|
static void scp_client_connect(WOLFSSH_CTX** ctx, WOLFSSH** ssh, int port,
|
|
const char* cmd)
|
|
{
|
|
WS_SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID;
|
|
SOCKADDR_IN_T clientAddr;
|
|
socklen_t clientAddrSz = sizeof(clientAddr);
|
|
int ret;
|
|
char* host = (char*)wolfSshIp;
|
|
const char* username = "jill";
|
|
const char* password = "upthehill";
|
|
|
|
if (ctx == NULL || ssh == NULL)
|
|
return;
|
|
|
|
*ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
if (*ctx == NULL)
|
|
return;
|
|
|
|
wolfSSH_CTX_SetPublicKeyCheck(*ctx, scpAcceptAnyServerHostKey);
|
|
wolfSSH_SetUserAuth(*ctx, scpUserAuth);
|
|
*ssh = wolfSSH_new(*ctx);
|
|
if (*ssh == NULL) {
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
return;
|
|
}
|
|
|
|
build_addr(&clientAddr, host, port);
|
|
tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family);
|
|
if (sockFd < 0) {
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz);
|
|
if (ret != 0) {
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
wolfSSH_SetUserAuthCtx(*ssh, (void*)password);
|
|
ret = wolfSSH_SetUsername(*ssh, username);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SetChannelType(*ssh, WOLFSSH_SESSION_EXEC, (byte*)cmd,
|
|
(word32)WSTRLEN(cmd));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_set_fd(*ssh, (int)sockFd);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_connect(*ssh);
|
|
|
|
if (ret != WS_SUCCESS) {
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Drives an SCP transfer with a forced mid-transfer rekey.
|
|
*
|
|
* toServer == 0: client SINK (wolfSSH_SCP_from), exercises ScpStreamRead, the
|
|
* confirmed hang path. toServer == 1: client SOURCE
|
|
* (wolfSSH_SCP_to), exercises the ScpStreamSend rekey/window
|
|
* drain loop. nonBlock drives the non-blocking retry path.
|
|
*/
|
|
static void scp_rekey_test(int nonBlock, int toServer)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
int ret;
|
|
int err;
|
|
int tries;
|
|
int kexCount = 0;
|
|
word32 i;
|
|
WS_SOCKET_T clientFd;
|
|
#ifdef USE_WINDOWS_API
|
|
DWORD rcvTimeout = 20000;
|
|
#else
|
|
struct timeval rcvTimeout;
|
|
#endif
|
|
byte fileData[SCP_REKEY_FILE_SZ];
|
|
char cmd[64];
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
/* Fixed names used for filesystem create/verify/cleanup. The *Buf copies
|
|
* are what get passed to the SCP API, which rewrites the path in place
|
|
* (rename/clean), so they cannot be reused to name the file afterward. The
|
|
* leading "./" keeps a directory component so the base-dir open succeeds,
|
|
* as the real scpclient passes $PWD-prefixed paths. */
|
|
const char* srcName = "./scp_rekey_src.txt";
|
|
const char* fromName = "./scp_rekey_from.txt";
|
|
const char* toName = "./scp_rekey_to.txt";
|
|
char srcBuf[32];
|
|
char fromBuf[32];
|
|
char toBuf[32];
|
|
const char* verifyName;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
/* mutable copies for the SCP API (rewritten in place during the transfer) */
|
|
WSTRNCPY(srcBuf, srcName, sizeof(srcBuf));
|
|
WSTRNCPY(fromBuf, fromName, sizeof(fromBuf));
|
|
WSTRNCPY(toBuf, toName, sizeof(toBuf));
|
|
|
|
/* deterministic source content */
|
|
for (i = 0; i < SCP_REKEY_FILE_SZ; i++)
|
|
fileData[i] = (byte)(i & 0xff);
|
|
AssertIntEQ(scpWriteTestFile(srcName, fileData, SCP_REKEY_FILE_SZ), 0);
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
/* -f: server is source (client SINK); -t: server is sink (client SOURCE) */
|
|
if (toServer) {
|
|
WSNPRINTF(cmd, sizeof(cmd), "scp -t %s", toName);
|
|
verifyName = toName;
|
|
}
|
|
else {
|
|
WSNPRINTF(cmd, sizeof(cmd), "scp -f %s", srcName);
|
|
verifyName = fromName;
|
|
}
|
|
|
|
scp_client_connect(&ctx, &ssh, ready.port, cmd);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Count keying completions from here on. The initial handshake already ran
|
|
* inside scp_client_connect, so kexCount stays 0 until the highwater-driven
|
|
* rekey fires mid-transfer; the AssertIntGT below then proves it did. */
|
|
wolfSSH_SetKeyingCompletionCb(ctx, scpKeyingCompleteCb);
|
|
wolfSSH_SetKeyingCompletionCbCtx(ssh, &kexCount);
|
|
|
|
/* handshake done in blocking mode; switch to non-blocking for the data
|
|
* phase so the WS_WANT_READ/WS_WANT_WRITE retry path is exercised */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
if (nonBlock)
|
|
tcp_set_nonblocking(&clientFd);
|
|
|
|
/* Bound the blocking-mode recv so a KEXINIT/rekey deadlock regression fails
|
|
* the AssertIntEQ below instead of hanging CI forever. The
|
|
* SCP_REKEY_MAX_TRIES bound only covers the non-blocking retry loop; a
|
|
* non-blocking socket never blocks in recv, so this is a no-op there. */
|
|
#ifdef USE_WINDOWS_API
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
(const char*)&rcvTimeout, sizeof(rcvTimeout));
|
|
#else
|
|
rcvTimeout.tv_sec = 20;
|
|
rcvTimeout.tv_usec = 0;
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
&rcvTimeout, sizeof(rcvTimeout));
|
|
#endif
|
|
|
|
/* 256 is well below the 2 KB payload, so the highwater check fires partway
|
|
* through and the ScpStreamRead/ScpStreamSend rekey handling must carry the
|
|
* transfer to completion. */
|
|
AssertIntEQ(wolfSSH_SetHighwater(ssh, 256), WS_SUCCESS);
|
|
|
|
/* The retry loop only applies to non-blocking. In blocking mode the
|
|
* ScpStreamRead/ScpStreamSend fixes must carry the rekey transparently, so
|
|
* a single call completes the transfer; gating on nonBlock keeps the
|
|
* blocking path from masking a regression that leaves WS_REKEYING set. */
|
|
tries = 0;
|
|
do {
|
|
if (toServer)
|
|
ret = wolfSSH_SCP_to(ssh, srcBuf, toBuf);
|
|
else
|
|
ret = wolfSSH_SCP_from(ssh, srcBuf, fromBuf);
|
|
err = wolfSSH_get_error(ssh);
|
|
/* tcp_select() waits for receive-readiness; on WS_WANT_WRITE it has no
|
|
* write event to wait on, so its 1s timeout is the intended (rare)
|
|
* fallback that yields the CPU instead of busy-spinning. */
|
|
if (nonBlock && ret != WS_SUCCESS && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING
|
|
|| err == WS_CHAN_RXD))
|
|
tcp_select(clientFd, 1);
|
|
tries++;
|
|
} while (nonBlock && ret != WS_SUCCESS && (err == WS_WANT_READ
|
|
|| err == WS_WANT_WRITE || err == WS_REKEYING
|
|
|| err == WS_CHAN_RXD)
|
|
&& tries <= SCP_REKEY_MAX_TRIES);
|
|
/* Fails fast (instead of hanging CI) if a regression keeps the transfer
|
|
* stuck in a want/rekey state past the retry bound. */
|
|
AssertIntLE(tries, SCP_REKEY_MAX_TRIES);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
|
|
/* A mid-transfer rekey must have fired; otherwise the test silently stops
|
|
* exercising the ScpStreamSend/ScpStreamRead rekey paths it was written
|
|
* for. */
|
|
AssertIntGT(kexCount, 0);
|
|
|
|
/* best-effort shutdown; the completed transfer above is the real assertion */
|
|
ret = wolfSSH_shutdown(ssh);
|
|
(void)ret;
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
|
|
/* verify the transferred file matches the source once the server is done */
|
|
AssertIntEQ(scpFilesMatch(verifyName, fileData, SCP_REKEY_FILE_SZ), 0);
|
|
|
|
WREMOVE(NULL, srcName);
|
|
WREMOVE(NULL, verifyName);
|
|
}
|
|
|
|
static void test_wolfSSH_SCP_ReKey(void)
|
|
{
|
|
scp_rekey_test(0, 0);
|
|
}
|
|
|
|
static void test_wolfSSH_SCP_ReKey_NonBlock(void)
|
|
{
|
|
scp_rekey_test(1, 0);
|
|
}
|
|
|
|
static void test_wolfSSH_SCP_ReKey_ToServer(void)
|
|
{
|
|
scp_rekey_test(0, 1);
|
|
}
|
|
|
|
static void test_wolfSSH_SCP_ReKey_ToServer_NonBlock(void)
|
|
{
|
|
scp_rekey_test(1, 1);
|
|
}
|
|
|
|
/* A send callback that returns 0 bytes on its first
|
|
* WOLFSSH_SCP_SINGLE_FILE_REQUEST (metadata now, data on the following call)
|
|
* must not make the server send the file header twice. The offset is static
|
|
* because the one-shot echoserver runs a single transfer. */
|
|
static byte scpZeroFirstData[SCP_REKEY_FILE_SZ];
|
|
static word32 scpZeroFirstOffset;
|
|
|
|
static int scpSendZeroFirst(WOLFSSH* ssh, int state, const char* peerRequest,
|
|
char* fileName, word32 fileNameSz, word64* mTime, word64* aTime,
|
|
int* fileMode, word32 fileOffset, word32* totalFileSz,
|
|
byte* buf, word32 bufSz, void* ctx)
|
|
{
|
|
word32 remain, n;
|
|
|
|
(void)ssh;
|
|
(void)peerRequest;
|
|
(void)fileOffset;
|
|
(void)ctx;
|
|
|
|
switch (state) {
|
|
case WOLFSSH_SCP_NEW_REQUEST:
|
|
return WS_SUCCESS;
|
|
|
|
case WOLFSSH_SCP_SINGLE_FILE_REQUEST:
|
|
/* fill metadata, but hand back zero data bytes on this first call */
|
|
WSTRNCPY(fileName, "scp_hdr_zero.txt", fileNameSz);
|
|
if (totalFileSz != NULL) *totalFileSz = SCP_REKEY_FILE_SZ;
|
|
if (mTime != NULL) *mTime = 0;
|
|
if (aTime != NULL) *aTime = 0;
|
|
if (fileMode != NULL) *fileMode = 0644;
|
|
scpZeroFirstOffset = 0;
|
|
return 0;
|
|
|
|
case WOLFSSH_SCP_CONTINUE_FILE_TRANSFER:
|
|
remain = SCP_REKEY_FILE_SZ - scpZeroFirstOffset;
|
|
if (remain == 0)
|
|
return WS_SCP_COMPLETE;
|
|
n = (remain < bufSz) ? remain : bufSz;
|
|
WMEMCPY(buf, scpZeroFirstData + scpZeroFirstOffset, n);
|
|
scpZeroFirstOffset += n;
|
|
return (int)n;
|
|
|
|
default:
|
|
return WS_SCP_ABORT;
|
|
}
|
|
}
|
|
|
|
/* Drives a real "scp -r" of a directory holding two files through the
|
|
* default filesystem send/recv callbacks. The header-dedup fix gates
|
|
* sending a file's header on a scpFileHeaderSent flag that gets reset when
|
|
* SCP_SEND_FILE loops back to SCP_TRANSFER for the next file in a recursive
|
|
* copy; this confirms that reset lets the second file get its own header
|
|
* instead of it being duplicated or skipped.
|
|
*
|
|
* Not run on Windows: this is the only end-to-end exercise of a recursive
|
|
* transfer anywhere in the suite (the example client cannot issue "scp -r -f",
|
|
* so scripts/scp.test never reaches it), and the received file does not match
|
|
* there. That is a Windows-side recursive SCP problem of its own, unrelated to
|
|
* the header fix, which reproduces on the default callbacks and needs its own
|
|
* investigation. */
|
|
#ifndef USE_WINDOWS_API
|
|
static void test_wolfSSH_SCP_RecursiveTwoFiles(void)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
int ret;
|
|
word32 i;
|
|
WS_SOCKET_T clientFd;
|
|
#ifdef USE_WINDOWS_API
|
|
DWORD rcvTimeout = 20000;
|
|
#else
|
|
struct timeval rcvTimeout;
|
|
#endif
|
|
const char* args[6];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
const char* dstDir = "./scp_recur_dst";
|
|
const char* out1 = "./scp_recur_dst/a_short.txt";
|
|
const char* out2 = "./scp_recur_dst/b_longer_name.txt";
|
|
byte data1[300];
|
|
byte data2[700];
|
|
char cmd[300];
|
|
char cwdBuf[200];
|
|
/* must hold all of srcBuf plus the longest name below, or gcc rejects the
|
|
* WSNPRINTF calls under -Werror=format-truncation */
|
|
char file1[300];
|
|
char file2[300];
|
|
/* wolfSSH_SCP_from() mutates its src/dst buffers in place (e.g.
|
|
* ScpCheckForRename() writes a NUL into the path), so these cannot be
|
|
* string literals. The client thread chdir()s into dstDir while
|
|
* receiving a directory; since the client and server here share one
|
|
* process (and thus one cwd), srcBuf must be absolute so the server
|
|
* thread's concurrent directory walk does not resolve relative to
|
|
* whatever directory the client just chdir()ed into. */
|
|
char srcBuf[256];
|
|
char dstBuf[32];
|
|
THREAD_TYPE serThread;
|
|
|
|
for (i = 0; i < sizeof(data1); i++)
|
|
data1[i] = (byte)((i * 3 + 1) & 0xff);
|
|
for (i = 0; i < sizeof(data2); i++)
|
|
data2[i] = (byte)((i * 5 + 2) & 0xff);
|
|
|
|
AssertNotNull(WGETCWD(NULL, cwdBuf, sizeof(cwdBuf)));
|
|
WSNPRINTF(srcBuf, sizeof(srcBuf), "%s/scp_recur_src", cwdBuf);
|
|
WSTRNCPY(dstBuf, dstDir, sizeof(dstBuf));
|
|
WSNPRINTF(file1, sizeof(file1), "%s/a_short.txt", srcBuf);
|
|
WSNPRINTF(file2, sizeof(file2), "%s/b_longer_name.txt", srcBuf);
|
|
|
|
/* full teardown first: a run that aborted mid-transfer leaves these
|
|
* behind, and then WMKDIR below fails with EEXIST, masking the real
|
|
* failure with a setup error */
|
|
WREMOVE(NULL, file1);
|
|
WREMOVE(NULL, file2);
|
|
WRMDIR(NULL, srcBuf);
|
|
WREMOVE(NULL, out1);
|
|
WREMOVE(NULL, out2);
|
|
WRMDIR(NULL, dstDir);
|
|
|
|
AssertIntEQ(WMKDIR(NULL, srcBuf, 0700), 0);
|
|
AssertIntEQ(scpWriteTestFile(file1, data1, sizeof(data1)), 0);
|
|
AssertIntEQ(scpWriteTestFile(file2, data2, sizeof(data2)), 0);
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
WSNPRINTF(cmd, sizeof(cmd), "scp -r -f %s", srcBuf);
|
|
scp_client_connect(&ctx, &ssh, ready.port, cmd);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* bound the recv so a regression fails the match assert below, not CI */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
#ifdef USE_WINDOWS_API
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
(const char*)&rcvTimeout, sizeof(rcvTimeout));
|
|
#else
|
|
rcvTimeout.tv_sec = 20;
|
|
rcvTimeout.tv_usec = 0;
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
&rcvTimeout, sizeof(rcvTimeout));
|
|
#endif
|
|
|
|
ret = wolfSSH_SCP_from(ssh, srcBuf, dstBuf);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
|
|
ret = wolfSSH_shutdown(ssh);
|
|
(void)ret;
|
|
|
|
WCLOSESOCKET(wolfSSH_get_fd(ssh));
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
|
|
/* a duplicated or skipped header on the second file corrupts the byte
|
|
* stream; an exact match on both files proves each got its own header */
|
|
AssertIntEQ(scpFilesMatch(out1, data1, sizeof(data1)), 0);
|
|
AssertIntEQ(scpFilesMatch(out2, data2, sizeof(data2)), 0);
|
|
|
|
WREMOVE(NULL, file1);
|
|
WREMOVE(NULL, file2);
|
|
WRMDIR(NULL, srcBuf);
|
|
WREMOVE(NULL, out1);
|
|
WREMOVE(NULL, out2);
|
|
WRMDIR(NULL, dstDir);
|
|
}
|
|
#else
|
|
static void test_wolfSSH_SCP_RecursiveTwoFiles(void) { ; }
|
|
#endif /* USE_WINDOWS_API */
|
|
|
|
static void test_wolfSSH_SCP_SendZeroFirst(void)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
int ret;
|
|
word32 i;
|
|
WS_SOCKET_T clientFd;
|
|
#ifdef USE_WINDOWS_API
|
|
DWORD rcvTimeout = 20000;
|
|
#else
|
|
struct timeval rcvTimeout;
|
|
#endif
|
|
const char* args[6];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
const char* srcName = "./scp_hdr_src.txt";
|
|
const char* fromName = "./scp_hdr_from.txt";
|
|
char srcBuf[32];
|
|
char fromBuf[32];
|
|
char cmd[64];
|
|
THREAD_TYPE serThread;
|
|
|
|
WSTRNCPY(srcBuf, srcName, sizeof(srcBuf));
|
|
WSTRNCPY(fromBuf, fromName, sizeof(fromBuf));
|
|
|
|
for (i = 0; i < SCP_REKEY_FILE_SZ; i++)
|
|
scpZeroFirstData[i] = (byte)((i * 7 + 1) & 0xff);
|
|
/* The on-disk file only satisfies the server's base-path parsing; the
|
|
* custom callback supplies the actual bytes, so a duplicated header shows
|
|
* up as a content mismatch below rather than a missing file. */
|
|
AssertIntEQ(scpWriteTestFile(srcName, scpZeroFirstData, SCP_REKEY_FILE_SZ),
|
|
0);
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
ser.scp_send = scpSendZeroFirst;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
WSNPRINTF(cmd, sizeof(cmd), "scp -f %s", srcName);
|
|
scp_client_connect(&ctx, &ssh, ready.port, cmd);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* bound the recv so a regression fails the match assert below, not CI */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
#ifdef USE_WINDOWS_API
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
(const char*)&rcvTimeout, sizeof(rcvTimeout));
|
|
#else
|
|
rcvTimeout.tv_sec = 20;
|
|
rcvTimeout.tv_usec = 0;
|
|
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
|
|
&rcvTimeout, sizeof(rcvTimeout));
|
|
#endif
|
|
|
|
ret = wolfSSH_SCP_from(ssh, srcBuf, fromBuf);
|
|
AssertIntEQ(ret, WS_SUCCESS);
|
|
|
|
ret = wolfSSH_shutdown(ssh);
|
|
(void)ret;
|
|
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
|
|
/* a duplicate header would corrupt the stream; an exact match proves the
|
|
* header was sent once */
|
|
AssertIntEQ(scpFilesMatch(fromName, scpZeroFirstData, SCP_REKEY_FILE_SZ), 0);
|
|
|
|
WREMOVE(NULL, srcName);
|
|
WREMOVE(NULL, fromName);
|
|
}
|
|
|
|
#else /* WOLFSSH_SCP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED &&
|
|
* !NO_FILESYSTEM && !WOLFSSH_SCP_USER_CALLBACKS && !WOLFSSH_ZEPHYR */
|
|
static void test_wolfSSH_SCP_ReKey(void) { ; }
|
|
static void test_wolfSSH_SCP_ReKey_NonBlock(void) { ; }
|
|
static void test_wolfSSH_SCP_ReKey_ToServer(void) { ; }
|
|
static void test_wolfSSH_SCP_ReKey_ToServer_NonBlock(void) { ; }
|
|
static void test_wolfSSH_SCP_SendZeroFirst(void) { ; }
|
|
static void test_wolfSSH_SCP_RecursiveTwoFiles(void) { ; }
|
|
#endif
|
|
|
|
|
|
#ifdef USE_WINDOWS_API
|
|
static byte color_test[] = {
|
|
0x1B, 0x5B, 0x34, 0x6D, 0x75, 0x6E, 0x64, 0x65,
|
|
0x72, 0x6C, 0x69, 0x6E, 0x65, 0x1B, 0x1B, 0x5B,
|
|
0x1B, 0x5B, 0x30, 0x6D, 0x0A, 0x1B, 0x5B, 0x33,
|
|
0x31, 0x6D, 0x72, 0x65, 0x64, 0x0A, 0x1B, 0x5B,
|
|
0x33, 0x32, 0x6D, 0x67, 0x72, 0x65, 0x65, 0x6E,
|
|
0x0A, 0x1B, 0x5B, 0x33, 0x33, 0x6D, 0x79, 0x65,
|
|
0x6C, 0x6C, 0x6F, 0x77, 0x0A, 0x1B, 0x5B, 0x32,
|
|
0x32, 0x6D, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x73,
|
|
0x65, 0x0A, 0x1B, 0x5B, 0x31, 0x6D, 0x62, 0x6F,
|
|
0x6C, 0x64, 0x0A, 0x1B, 0x5B, 0x33, 0x34, 0x6D,
|
|
0x62, 0x6C, 0x75, 0x65, 0x0A, 0x1B, 0x5B, 0x33,
|
|
0x35, 0x6D, 0x6D, 0x61, 0x67, 0x65, 0x6E, 0x74,
|
|
0x61, 0x0A, 0x1B, 0x5B, 0x33, 0x36, 0x6D, 0x63,
|
|
0x79, 0x61, 0x6E, 0x0A, 0x1B, 0x5B, 0x33, 0x37,
|
|
0x6D, 0x77, 0x68, 0x69, 0x74, 0x65, 0x0A, 0x1B,
|
|
0x5B, 0x30, 0x6D, 0x6E, 0x6F, 0x72, 0x6D, 0x61,
|
|
0x6C, 0x0A, 0x1B, 0x5B, 0x34, 0x30, 0x6D, 0x62,
|
|
0x6C, 0x61, 0x63, 0x6B, 0x20, 0x62, 0x67, 0x0A,
|
|
0x1B, 0x5B, 0x34, 0x31, 0x6D, 0x72, 0x65, 0x64,
|
|
0x20, 0x62, 0x67, 0x0A, 0x1B, 0x5B, 0x34, 0x32,
|
|
0x6D, 0x67, 0x72, 0x65, 0x65, 0x6E, 0x20, 0x62,
|
|
0x67, 0x0A, 0x1B, 0x5B, 0x34, 0x33, 0x6D, 0x62,
|
|
0x72, 0x6F, 0x77, 0x6E, 0x20, 0x62, 0x67, 0x0A,
|
|
0x1B, 0x5B, 0x34, 0x34, 0x6D, 0x62, 0x6C, 0x75,
|
|
0x65, 0x20, 0x62, 0x67, 0x0A, 0x1B, 0x5B, 0x34,
|
|
0x35, 0x6D, 0x6D, 0x61, 0x67, 0x65, 0x6E, 0x74,
|
|
0x61, 0x20, 0x62, 0x67, 0x0A, 0x1B, 0x5B, 0x34,
|
|
0x36, 0x6D, 0x63, 0x79, 0x61, 0x6E, 0x20, 0x62,
|
|
0x67, 0x0A, 0x1B, 0x5B, 0x34, 0x37, 0x6D, 0x77,
|
|
0x68, 0x69, 0x74, 0x65, 0x20, 0x62, 0x67, 0x0A,
|
|
0x1B, 0x5B, 0x34, 0x39, 0x6D, 0x64, 0x65, 0x66,
|
|
0x61, 0x75, 0x6C, 0x74, 0x20, 0x62, 0x67, 0x0A,
|
|
};
|
|
|
|
/* OSC (ESC ]) sequences that end before the command is complete. These
|
|
* exercise the bounds checks in wolfSSH_DoOSC; each truncated sequence is
|
|
* dropped (WS_SUCCESS) without reading past the end of the buffer. */
|
|
static byte osc_trunc_cmd[] = { /* ends right after "ESC ]" */
|
|
0x1B, 0x5D
|
|
};
|
|
static byte osc_trunc_arg[] = { /* ends after "ESC ] 0" */
|
|
0x1B, 0x5D, 0x30
|
|
};
|
|
static byte osc_trunc_str[] = { /* "ESC ] 0 ; title" with no BEL terminator */
|
|
0x1B, 0x5D, 0x30, 0x3B, 0x74, 0x69, 0x74, 0x6C, 0x65
|
|
};
|
|
static byte osc_full[] = { /* well formed "ESC ] 0 ; hi BEL" */
|
|
0x1B, 0x5D, 0x30, 0x3B, 0x68, 0x69, 0x07
|
|
};
|
|
static byte csi_open[] = { /* "ESC [" with no args yet, escBufSz left at 0 */
|
|
0x1B, 0x5B
|
|
};
|
|
static byte csi_args[] = { /* pure args "12" with no command char */
|
|
0x31, 0x32
|
|
};
|
|
static byte csi_cmd[] = { /* command char 'm' completes the sequence */
|
|
0x6D
|
|
};
|
|
static byte csi_inline[] = { /* "ESC [ 1 2" args run to end of one buffer */
|
|
0x1B, 0x5B, 0x31, 0x32
|
|
};
|
|
#endif /* USE_WINDOWS_API */
|
|
|
|
|
|
static void test_wolfSSH_ConvertConsole(void)
|
|
{
|
|
#ifdef USE_WINDOWS_API
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
int i = 3, j = 4; /* arbitrary value */
|
|
const char err[] = "test setting error msg";
|
|
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
AssertIntNE(WS_SUCCESS, wolfSSH_SetUsername(NULL, NULL));
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
|
|
/* parameter tests */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(NULL, stdoutHandle, color_test,
|
|
sizeof(color_test)), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, NULL,
|
|
sizeof(color_test)), WS_BAD_ARGUMENT);
|
|
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, color_test, 1),
|
|
WS_WANT_READ);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, color_test + 1, 1),
|
|
WS_WANT_READ);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, color_test + 2,
|
|
sizeof(color_test) - 2), WS_SUCCESS);
|
|
|
|
/* bad esc esc command */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, color_test, 1),
|
|
WS_WANT_READ);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, color_test, 1),
|
|
WS_SUCCESS); /* should skip over unknown console code */
|
|
|
|
/* truncated OSC sequences must be dropped without an out of bounds read */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, osc_trunc_cmd,
|
|
sizeof(osc_trunc_cmd)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, osc_trunc_arg,
|
|
sizeof(osc_trunc_arg)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, osc_trunc_str,
|
|
sizeof(osc_trunc_str)), WS_SUCCESS);
|
|
/* a well formed OSC sequence still parses */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, osc_full,
|
|
sizeof(osc_full)), WS_SUCCESS);
|
|
|
|
/* a CSI sequence split so the first packet ends right after "ESC [" and
|
|
* the second carries only argument bytes must not read past the buffer
|
|
* while waiting for the command char */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_open,
|
|
sizeof(csi_open)), WS_WANT_READ);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_args,
|
|
sizeof(csi_args)), WS_WANT_READ);
|
|
/* the trailing command char completes the reassembled sequence */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_cmd,
|
|
sizeof(csi_cmd)), WS_SUCCESS);
|
|
/* after the split sequence completes the esc state must be cleared, so a
|
|
* following plain argument byte is printed rather than swallowed back into
|
|
* CSI parsing (which would return WS_WANT_READ) */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_args, 1),
|
|
WS_SUCCESS);
|
|
|
|
/* a single buffer whose CSI arguments run to the end with no command char
|
|
* must save the partial args and wait, then complete on the next byte */
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_inline,
|
|
sizeof(csi_inline)), WS_WANT_READ);
|
|
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_cmd,
|
|
sizeof(csi_cmd)), WS_SUCCESS);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#endif /* USE_WINDOWS_API */
|
|
}
|
|
|
|
|
|
static void test_wstrcat(void)
|
|
{
|
|
#ifndef WSTRING_USER
|
|
char dst[5];
|
|
|
|
WSTRNCPY(dst, "12", sizeof(dst));
|
|
AssertNull(wstrncat(dst, "345", sizeof(dst)));
|
|
AssertStrEQ(dst, "12");
|
|
AssertNotNull(wstrncat(dst, "67", sizeof(dst)));
|
|
AssertStrEQ(dst, "1267");
|
|
#endif /* WSTRING_USER */
|
|
}
|
|
|
|
|
|
#if defined(DEBUG_WOLFSSH) || defined(WOLFSSH_SSHD)
|
|
static char logCaptureBuf[256];
|
|
static void logCaptureCb(enum wolfSSH_LogLevel level, const char* msg)
|
|
{
|
|
(void)level;
|
|
WSTRNCPY(logCaptureBuf, msg, sizeof(logCaptureBuf));
|
|
logCaptureBuf[sizeof(logCaptureBuf) - 1] = '\0';
|
|
}
|
|
#endif
|
|
|
|
static void test_wolfSSH_Log_sanitize(void)
|
|
{
|
|
#if defined(DEBUG_WOLFSSH) || defined(WOLFSSH_SSHD)
|
|
/* This installs a capture callback that is intentionally left in place:
|
|
* wolfSSH_SetLoggingCb() ignores NULL and the default callback has internal
|
|
* linkage, so there is no way to restore it. This is safe for the suite
|
|
* because logging is disabled by default (wolfSSH_LogEnabled() is 0, so
|
|
* WLOG() sinks do not fire) and no later test inspects log output. If a
|
|
* future test needs the default sink, restore it here instead. */
|
|
wolfSSH_SetLoggingCb(logCaptureCb);
|
|
|
|
/* an embedded CR/LF in a %s argument must not reach the sink intact */
|
|
logCaptureBuf[0] = '\0';
|
|
wolfSSH_Log(WS_LOG_DEBUG, "value = %s", "a\r\nFORGED [INFO] ok");
|
|
AssertNotNull(WSTRSTR(logCaptureBuf, "value = a"));
|
|
AssertNull(WSTRCHR(logCaptureBuf, '\n'));
|
|
AssertNull(WSTRCHR(logCaptureBuf, '\r'));
|
|
|
|
/* other control bytes and DEL are scrubbed; tab is preserved */
|
|
logCaptureBuf[0] = '\0';
|
|
wolfSSH_Log(WS_LOG_DEBUG, "%s", "x\033[31m\177\ty");
|
|
AssertNull(WSTRCHR(logCaptureBuf, '\033'));
|
|
AssertNull(WSTRCHR(logCaptureBuf, '\177'));
|
|
AssertNotNull(WSTRCHR(logCaptureBuf, '\t'));
|
|
|
|
/* clean strings pass through unchanged */
|
|
logCaptureBuf[0] = '\0';
|
|
wolfSSH_Log(WS_LOG_DEBUG, "ssh-userauth %u", 22u);
|
|
AssertStrEQ(logCaptureBuf, "ssh-userauth 22");
|
|
#endif /* DEBUG_WOLFSSH || WOLFSSH_SSHD */
|
|
}
|
|
|
|
|
|
#if (defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)) && \
|
|
!defined(NO_WOLFSSH_SERVER)
|
|
struct RealPathTestCase {
|
|
const char* in;
|
|
const char* exp;
|
|
};
|
|
|
|
/* On Zephyr, wolfSSH_RealPath preserves the trailing slash after a drive-root
|
|
* colon (e.g. /C:/) rather than stripping it (e.g. /C:), due to the
|
|
* WOLFSSH_ZEPHYR guard in the ".." handler. */
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
#define WOLFSSH_TEST_DRIVE_ROOT "/C:/"
|
|
#else
|
|
#define WOLFSSH_TEST_DRIVE_ROOT "/C:"
|
|
#endif
|
|
|
|
struct RealPathTestCase realPathDefault[] = {
|
|
{ ".", "/C:/Users/fred" },
|
|
{ "", "/C:/Users/fred" },
|
|
{ "/C:/Users/fred/..", "/C:/Users" },
|
|
{ "..", "/C:/Users" },
|
|
{ "../..", WOLFSSH_TEST_DRIVE_ROOT },
|
|
{ "../barney", "/C:/Users/barney" },
|
|
{ "/C:/Users/..", WOLFSSH_TEST_DRIVE_ROOT },
|
|
{ "/C:/..", "/" },
|
|
{ "/C:/../../../../../../../..", "/" },
|
|
{ "/", "/" },
|
|
{ "/C:/Users/fred/../..", WOLFSSH_TEST_DRIVE_ROOT },
|
|
{ "/C:/Users/fred/././././.", "/C:/Users/fred" },
|
|
{ "/C:/Users/fred/../././..", WOLFSSH_TEST_DRIVE_ROOT },
|
|
{ "./.ssh", "/C:/Users/fred/.ssh" },
|
|
{ "./.ssh/../foo", "/C:/Users/fred/foo" },
|
|
{ "./.ssh/../foo", "/C:/Users/fred/foo" },
|
|
{ "///home//////////fred///", "/home/fred" },
|
|
{ "/home/C:/ok", "/home/C:/ok" },
|
|
{ "/home/fred/frob/frizz/../../../barney/bar/baz/./././../..",
|
|
"/home/barney" },
|
|
{ "/home/fred/sample.", "/home/fred/sample." },
|
|
{ "/home/fred/sample.jpg", "/home/fred/sample.jpg" },
|
|
{ "/home/fred/sample./other", "/home/fred/sample./other" },
|
|
{ "/home/fred/sample.dir/other", "/home/fred/sample.dir/other" },
|
|
{ "./sample.", "/C:/Users/fred/sample." },
|
|
{ "./sample.jpg", "/C:/Users/fred/sample.jpg" },
|
|
{ "./sample./other", "/C:/Users/fred/sample./other" },
|
|
{ "./sample.dir/other", "/C:/Users/fred/sample.dir/other" },
|
|
{ "\\C:\\Users\\fred\\Documents\\junk.txt",
|
|
"/C:/Users/fred/Documents/junk.txt" },
|
|
{ "C:\\Users\\fred\\Documents\\junk.txt",
|
|
"/C:/Users/fred/Documents/junk.txt" },
|
|
{ "/C:\\Users\\fred/Documents\\junk.txt",
|
|
"/C:/Users/fred/Documents/junk.txt" },
|
|
/* Root-preservation / canonicalization of leading ".." */
|
|
{ "/../etc/passwd", "/etc/passwd" },
|
|
{ "/../../../etc/passwd", "/etc/passwd" },
|
|
{ "/C:/../../etc/passwd", "/etc/passwd" },
|
|
};
|
|
|
|
struct RealPathTestCase realPathNull[] = {
|
|
{ ".", "/" },
|
|
{ "", "/" },
|
|
{ "..", "/" },
|
|
{ "../barney", "/barney" },
|
|
{ "/../etc/passwd", "/etc/passwd" },
|
|
{ "/../../../etc/passwd", "/etc/passwd" },
|
|
};
|
|
|
|
static void DoRealPathTestCase(const char* path, struct RealPathTestCase* tc)
|
|
{
|
|
char testPath[128];
|
|
char checkPath[128];
|
|
int err;
|
|
|
|
WSTRNCPY(testPath, tc->in, sizeof(testPath) - 1);
|
|
testPath[sizeof(testPath) - 1] = 0;
|
|
WMEMSET(checkPath, 0, sizeof checkPath);
|
|
err = wolfSSH_RealPath(path, testPath,
|
|
checkPath, sizeof checkPath);
|
|
AssertIntEQ(err, WS_SUCCESS);
|
|
AssertStrEQ(tc->exp, checkPath);
|
|
}
|
|
|
|
|
|
struct RealPathTestFailCase {
|
|
const char* defaultPath;
|
|
const char* in;
|
|
word32 checkPathSz;
|
|
int expErr;
|
|
};
|
|
struct RealPathTestFailCase realPathFail[] = {
|
|
/* Output size less than default path length. */
|
|
{ "12345678", "12345678", 4, WS_INVALID_PATH_E },
|
|
/* Output size equal to default path length. */
|
|
{ "12345678", "12345678", 8, WS_INVALID_PATH_E },
|
|
/* Copy segment will not fit in output. */
|
|
{ "1234567", "12345678", 8, WS_INVALID_PATH_E },
|
|
/* Separator plus segment must leave room for the NUL. */
|
|
{ NULL, "aaa/bbb", 8, WS_INVALID_PATH_E },
|
|
};
|
|
|
|
static void DoRealPathTestFailCase(struct RealPathTestFailCase* tc)
|
|
{
|
|
char testPath[128];
|
|
char checkPath[128];
|
|
int err;
|
|
|
|
WSTRNCPY(testPath, tc->in, sizeof(testPath) - 1);
|
|
testPath[sizeof(testPath) - 1] = 0;
|
|
WMEMSET(checkPath, 0, sizeof checkPath);
|
|
err = wolfSSH_RealPath(tc->defaultPath, testPath,
|
|
checkPath, tc->checkPathSz);
|
|
AssertIntEQ(err, tc->expErr);
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_RealPath(void)
|
|
{
|
|
word32 testCount;
|
|
word32 i;
|
|
|
|
testCount = (sizeof realPathDefault)/(sizeof(struct RealPathTestCase));
|
|
for (i = 0; i < testCount; i++) {
|
|
DoRealPathTestCase("/C:/Users/fred", realPathDefault + i);
|
|
}
|
|
|
|
testCount = (sizeof realPathNull)/(sizeof(struct RealPathTestCase));
|
|
for (i = 0; i < testCount; i++) {
|
|
DoRealPathTestCase(NULL, realPathNull + i);
|
|
}
|
|
|
|
testCount = (sizeof realPathFail)/(sizeof(struct RealPathTestFailCase));
|
|
for (i = 0; i < testCount; i++) {
|
|
DoRealPathTestFailCase(realPathFail + i);
|
|
}
|
|
}
|
|
#else
|
|
static void test_wolfSSH_RealPath(void) { ; }
|
|
#endif
|
|
|
|
|
|
static void test_wolfSSH_SetMaxAuthAttempts(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
int defaultValue;
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* NULL is rejected. */
|
|
AssertIntEQ(wolfSSH_CTX_SetMaxAuthAttempts(NULL, 3), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_CTX_GetMaxAuthAttempts(NULL), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_SetMaxAuthAttempts(NULL, 3), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_GetMaxAuthAttempts(NULL), WS_BAD_ARGUMENT);
|
|
|
|
defaultValue = wolfSSH_CTX_GetMaxAuthAttempts(ctx);
|
|
AssertIntGT(defaultValue, 0);
|
|
|
|
/* A positive value is accepted. */
|
|
AssertIntEQ(wolfSSH_CTX_SetMaxAuthAttempts(ctx, 3), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_GetMaxAuthAttempts(ctx), 3);
|
|
|
|
/* Zero and negative values restore the default without error. */
|
|
AssertIntEQ(wolfSSH_CTX_SetMaxAuthAttempts(ctx, 0), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_GetMaxAuthAttempts(ctx), defaultValue);
|
|
AssertIntEQ(wolfSSH_CTX_SetMaxAuthAttempts(ctx, -1), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_GetMaxAuthAttempts(ctx), defaultValue);
|
|
|
|
/* A session inherits the CTX value and can override it. */
|
|
AssertIntEQ(wolfSSH_CTX_SetMaxAuthAttempts(ctx, 4), WS_SUCCESS);
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
AssertIntEQ(wolfSSH_GetMaxAuthAttempts(ssh), 4);
|
|
AssertIntEQ(wolfSSH_SetMaxAuthAttempts(ssh, 2), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_GetMaxAuthAttempts(ssh), 2);
|
|
AssertIntEQ(wolfSSH_CTX_GetMaxAuthAttempts(ctx), 4);
|
|
AssertIntEQ(wolfSSH_SetMaxAuthAttempts(ssh, 0), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_GetMaxAuthAttempts(ssh), defaultValue);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
static void test_wolfSSH_SetAlgoList(void)
|
|
{
|
|
const char* newKexList;
|
|
const char* newKeyList;
|
|
const char* newCipherList;
|
|
const char* newMacList;
|
|
const char* newKeyAccList;
|
|
word32 queryIdx;
|
|
const char* defaultKexList = NULL;
|
|
const char* defaultKeyList = NULL;
|
|
const char* defaultCipherList = NULL;
|
|
const char* defaultMacList = NULL;
|
|
const char* defaultKeyAccList = NULL;
|
|
const char* checkKexList = NULL;
|
|
const char* checkKeyList = NULL;
|
|
const char* checkCipherList = NULL;
|
|
const char* checkMacList = NULL;
|
|
const char* checkKeyAccList = NULL;
|
|
const char* rawKey = NULL;
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
byte* key;
|
|
word32 keySz;
|
|
|
|
/* Use algorithms compiled into this build so reduced-crypto configs don't
|
|
* break the test; Query* returns a stable name pointer. KeyAccepted is a
|
|
* TYPE_KEY list. */
|
|
queryIdx = 0; newKexList = wolfSSH_QueryKex(&queryIdx);
|
|
queryIdx = 0; newKeyList = wolfSSH_QueryKey(&queryIdx);
|
|
queryIdx = 0; newCipherList = wolfSSH_QueryCipher(&queryIdx);
|
|
queryIdx = 0; newMacList = wolfSSH_QueryMac(&queryIdx);
|
|
newKeyAccList = newKeyList;
|
|
AssertNotNull(newKexList);
|
|
AssertNotNull(newKeyList);
|
|
AssertNotNull(newCipherList);
|
|
AssertNotNull(newMacList);
|
|
|
|
/* Create a ctx object. */
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* Check that the ctx's default algo lists are not null */
|
|
defaultKexList = wolfSSH_CTX_GetAlgoListKex(ctx);
|
|
AssertNotNull(defaultKexList);
|
|
|
|
defaultKeyList = wolfSSH_CTX_GetAlgoListKey(ctx);
|
|
AssertNotNull(defaultKeyList);
|
|
|
|
defaultCipherList = wolfSSH_CTX_GetAlgoListCipher(ctx);
|
|
AssertNotNull(defaultCipherList);
|
|
|
|
defaultMacList = wolfSSH_CTX_GetAlgoListMac(ctx);
|
|
AssertNotNull(defaultMacList);
|
|
|
|
defaultKeyAccList = wolfSSH_CTX_GetAlgoListKeyAccepted(ctx);
|
|
AssertNotNull(defaultKeyAccList);
|
|
|
|
/* Create a new ssh object. */
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Check that the ssh's default algo lists match the ctx's algo lists. */
|
|
checkKexList = wolfSSH_GetAlgoListKex(ssh);
|
|
AssertPtrEq(checkKexList, defaultKexList);
|
|
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertPtrEq(checkKeyList, defaultKeyList);
|
|
|
|
checkCipherList = wolfSSH_GetAlgoListCipher(ssh);
|
|
AssertPtrEq(checkCipherList, defaultCipherList);
|
|
|
|
checkMacList = wolfSSH_GetAlgoListMac(ssh);
|
|
AssertPtrEq(checkMacList, defaultMacList);
|
|
|
|
checkKeyAccList = wolfSSH_GetAlgoListKeyAccepted(ssh);
|
|
AssertPtrEq(checkKeyAccList, defaultKeyAccList);
|
|
|
|
/* Set the ssh's algo lists, check they match new value. */
|
|
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, newKexList), WS_SUCCESS);
|
|
checkKexList = wolfSSH_GetAlgoListKex(ssh);
|
|
AssertPtrEq(checkKexList, newKexList);
|
|
|
|
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, newKeyList), WS_SUCCESS);
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertPtrEq(checkKeyList, newKeyList);
|
|
|
|
AssertIntEQ(wolfSSH_SetAlgoListCipher(ssh, newCipherList), WS_SUCCESS);
|
|
checkCipherList = wolfSSH_GetAlgoListCipher(ssh);
|
|
AssertPtrEq(checkCipherList, newCipherList);
|
|
|
|
AssertIntEQ(wolfSSH_SetAlgoListMac(ssh, newMacList), WS_SUCCESS);
|
|
checkMacList = wolfSSH_GetAlgoListMac(ssh);
|
|
AssertPtrEq(checkMacList, newMacList);
|
|
|
|
AssertIntEQ(wolfSSH_SetAlgoListKeyAccepted(ssh, newKeyAccList), WS_SUCCESS);
|
|
checkKeyAccList = wolfSSH_GetAlgoListKeyAccepted(ssh);
|
|
AssertPtrEq(checkKeyAccList, newKeyAccList);
|
|
|
|
/* Delete the ssh. */
|
|
wolfSSH_free(ssh);
|
|
|
|
/* Set new algo lists on the ctx. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKex(ctx, newKexList), WS_SUCCESS);
|
|
defaultKexList = wolfSSH_CTX_GetAlgoListKex(ctx);
|
|
AssertPtrEq(defaultKexList, newKexList);
|
|
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(ctx, newKeyList), WS_SUCCESS);
|
|
defaultKeyList = wolfSSH_CTX_GetAlgoListKey(ctx);
|
|
AssertPtrEq(defaultKeyList, newKeyList);
|
|
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, newCipherList), WS_SUCCESS);
|
|
defaultCipherList = wolfSSH_CTX_GetAlgoListCipher(ctx);
|
|
AssertNotNull(defaultCipherList);
|
|
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListMac(ctx, newMacList), WS_SUCCESS);
|
|
defaultMacList = wolfSSH_CTX_GetAlgoListMac(ctx);
|
|
AssertNotNull(defaultMacList);
|
|
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx, newKeyAccList),
|
|
WS_SUCCESS);
|
|
defaultKeyAccList = wolfSSH_CTX_GetAlgoListKeyAccepted(ctx);
|
|
AssertNotNull(defaultKeyAccList);
|
|
|
|
/* Create a new ssh object. */
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Check that the ssh's default algo lists match the ctx's algo lists. */
|
|
checkKexList = wolfSSH_GetAlgoListKex(ssh);
|
|
AssertPtrEq(checkKexList, defaultKexList);
|
|
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertPtrEq(checkKeyList, defaultKeyList);
|
|
|
|
checkCipherList = wolfSSH_GetAlgoListCipher(ssh);
|
|
AssertPtrEq(checkCipherList, defaultCipherList);
|
|
|
|
checkMacList = wolfSSH_GetAlgoListMac(ssh);
|
|
AssertPtrEq(checkMacList, defaultMacList);
|
|
|
|
checkKeyAccList = wolfSSH_GetAlgoListKeyAccepted(ssh);
|
|
AssertPtrEq(checkKeyAccList, defaultKeyAccList);
|
|
|
|
/* Cleanup */
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
/* Create a ctx object. */
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
/* Check server ctx's key list is NULL. */
|
|
defaultKeyList = wolfSSH_CTX_GetAlgoListKey(ctx);
|
|
AssertNull(defaultKeyList);
|
|
defaultKeyAccList = wolfSSH_CTX_GetAlgoListKeyAccepted(ctx);
|
|
AssertNotNull(defaultKeyAccList);
|
|
|
|
/* Create a new ssh object. */
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Check server ssh's key list is NULL. */
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertNull(checkKeyList);
|
|
|
|
/* Delete the ssh. */
|
|
wolfSSH_free(ssh);
|
|
|
|
/* Set key on ctx. */
|
|
#if !defined(WOLFSSH_NO_ECDSA)
|
|
rawKey = serverKeyEccDer;
|
|
#elif !defined(WOLFSSH_NO_RSA)
|
|
rawKey = serverKeyRsaDer;
|
|
#endif
|
|
AssertNotNull(rawKey);
|
|
AssertIntEQ(0,
|
|
ConvertHexToBin(rawKey, &key, &keySz,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
|
|
AssertIntEQ(WS_SUCCESS,
|
|
wolfSSH_CTX_UsePrivateKey_buffer(ctx,
|
|
key, keySz, WOLFSSH_FORMAT_ASN1));
|
|
|
|
/* Check ctx's key algo list is still null. */
|
|
checkKeyList = wolfSSH_CTX_GetAlgoListKey(ctx);
|
|
AssertNull(checkKeyList);
|
|
|
|
/* Create a new ssh object. */
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* Check ssh's key algo list is null. */
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertNull(checkKeyList);
|
|
|
|
/* Set a new list on ssh. */
|
|
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, newKeyList), WS_SUCCESS);
|
|
checkKeyList = wolfSSH_GetAlgoListKey(ssh);
|
|
AssertPtrEq(checkKeyList, newKeyList);
|
|
|
|
/* NULL restores the server auto-derive default for the key lists. */
|
|
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, NULL), WS_SUCCESS);
|
|
AssertNull(wolfSSH_GetAlgoListKey(ssh));
|
|
AssertIntEQ(wolfSSH_SetAlgoListKeyAccepted(ssh, NULL), WS_SUCCESS);
|
|
AssertNull(wolfSSH_GetAlgoListKeyAccepted(ssh));
|
|
|
|
/* Cleanup */
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
FreeBins(key, NULL, NULL, NULL);
|
|
}
|
|
|
|
|
|
/* Exercise CheckAlgoList()'s rejection paths through the public setters. */
|
|
static void test_wolfSSH_CheckAlgoList(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
const char* validCipher;
|
|
const char* secondCipher;
|
|
const char* aKexName;
|
|
char listBuf[128];
|
|
word32 queryIdx;
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
queryIdx = 0;
|
|
validCipher = wolfSSH_QueryCipher(&queryIdx);
|
|
secondCipher = wolfSSH_QueryCipher(&queryIdx);
|
|
queryIdx = 0; aKexName = wolfSSH_QueryKex(&queryIdx);
|
|
AssertNotNull(validCipher);
|
|
AssertNotNull(aKexName);
|
|
|
|
/* Every built-in default must pass its own setter, in any build config. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKex(ctx,
|
|
wolfSSH_CTX_GetAlgoListKex(ctx)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx,
|
|
wolfSSH_CTX_GetAlgoListCipher(ctx)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListMac(ctx,
|
|
wolfSSH_CTX_GetAlgoListMac(ctx)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(ctx,
|
|
wolfSSH_CTX_GetAlgoListKey(ctx)), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx,
|
|
wolfSSH_CTX_GetAlgoListKeyAccepted(ctx)), WS_SUCCESS);
|
|
|
|
/* A list with no usable name in it is rejected. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, "not-an-algorithm"),
|
|
WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, "not-one,nor-this"),
|
|
WS_INVALID_ALGO_ID);
|
|
|
|
/* Category mismatch: a KEX name in the cipher slot is rejected. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, aKexName),
|
|
WS_INVALID_ALGO_ID);
|
|
|
|
/* Empty and all-comma lists are rejected. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, ""), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, ",,,"), WS_INVALID_ALGO_ID);
|
|
|
|
/* A rejected list must leave the previous value intact. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, validCipher), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, "bogus"),
|
|
WS_INVALID_ALGO_ID);
|
|
AssertPtrEq(wolfSSH_CTX_GetAlgoListCipher(ctx), validCipher);
|
|
|
|
/* One trailing comma is tolerated; the canned lists carry one. */
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,", validCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS);
|
|
|
|
/* Any other empty element would reach KEXINIT as a zero-length name. */
|
|
WSNPRINTF(listBuf, sizeof(listBuf), ",%s", validCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf),
|
|
WS_INVALID_ALGO_ID);
|
|
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,,", validCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf),
|
|
WS_INVALID_ALGO_ID);
|
|
|
|
/* An unknown name is skipped, so one superset list works on any build. */
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,bogus", validCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS);
|
|
|
|
/* A known name in the wrong category still fails the whole list. */
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,%s", validCipher, aKexName);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf),
|
|
WS_INVALID_ALGO_ID);
|
|
|
|
/* Reduced-crypto builds may have only one cipher. */
|
|
if (secondCipher != NULL) {
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,%s",
|
|
validCipher, secondCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS);
|
|
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "bogus,%s", secondCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS);
|
|
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "%s,,%s",
|
|
validCipher, secondCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf),
|
|
WS_INVALID_ALGO_ID);
|
|
}
|
|
|
|
/* "none" names no host key, and needs the build flag for cipher/MAC. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(ctx, "none"), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx, "none"),
|
|
WS_INVALID_ALGO_ID);
|
|
#ifndef WOLFSSH_ALLOW_NONE_CIPHER
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, "none"), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListMac(ctx, "none"), WS_INVALID_ALGO_ID);
|
|
#else
|
|
/* With --enable-none-cipher, "none" is a valid cipher/MAC selection (an
|
|
* insecure, testing-only plaintext transport) and must round-trip through
|
|
* the setters. The setters store the caller's pointer, so a static literal
|
|
* is used and a real list restored below before listBuf goes out of scope. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, "none"), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListMac(ctx, "none"), WS_SUCCESS);
|
|
AssertStrEQ(wolfSSH_CTX_GetAlgoListCipher(ctx), "none");
|
|
AssertStrEQ(wolfSSH_CTX_GetAlgoListMac(ctx), "none");
|
|
/* "none" mixed into a list is still accepted alongside a real cipher. */
|
|
WSNPRINTF(listBuf, sizeof(listBuf), "none,%s", validCipher);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, listBuf), WS_SUCCESS);
|
|
#endif
|
|
|
|
/* Restore a static list; the setters store the caller's pointer and
|
|
* listBuf goes out of scope at return. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, validCipher), WS_SUCCESS);
|
|
|
|
/* NULL is rejected for kex/cipher/mac, accepted for the key lists. */
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKex(ctx, NULL), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListCipher(ctx, NULL), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListMac(ctx, NULL), WS_INVALID_ALGO_ID);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(ctx, NULL), WS_SUCCESS);
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx, NULL), WS_SUCCESS);
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
/* Client: NULL rejected for Key, accepted for KeyAccepted. */
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
AssertNotNull(ctx);
|
|
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKey(ctx, NULL), WS_INVALID_ALGO_ID);
|
|
AssertNotNull(wolfSSH_CTX_GetAlgoListKey(ctx));
|
|
AssertIntEQ(wolfSSH_CTX_SetAlgoListKeyAccepted(ctx, NULL), WS_SUCCESS);
|
|
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, NULL), WS_INVALID_ALGO_ID);
|
|
AssertNotNull(wolfSSH_GetAlgoListKey(ssh));
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_FWD
|
|
|
|
/* Argument validation for the remote-forward request APIs. Only the rejection
|
|
* paths are exercised here; sending a real request needs a live session, which
|
|
* scripts/fwd.test covers. */
|
|
static void test_wolfSSH_FwdRemote_badArgs(void)
|
|
{
|
|
WOLFSSH_CTX* ctx;
|
|
WOLFSSH* ssh;
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
AssertNotNull(ctx);
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
/* NULL session or bind address. */
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(NULL, "0.0.0.0", 22, 1),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, NULL, 22, 1), WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(NULL, "0.0.0.0", 22, 1),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, NULL, 22, 1), WS_BAD_ARGUMENT);
|
|
|
|
/* A port is 16 bits on the wire. */
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, "0.0.0.0", 65536, 1),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, "0.0.0.0", 65536, 1),
|
|
WS_BAD_ARGUMENT);
|
|
|
|
/* Port 0 asks the peer to allocate, so it cannot name one to cancel. */
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, "0.0.0.0", 0, 1),
|
|
WS_BAD_ARGUMENT);
|
|
|
|
/* The reply is the only place a port-0 request learns its port. */
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, "0.0.0.0", 0, 0),
|
|
WS_BAD_ARGUMENT);
|
|
|
|
/* wantReply is a boolean. */
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, "0.0.0.0", 22, 2),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, "0.0.0.0", 22, -1),
|
|
WS_BAD_ARGUMENT);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
|
|
/* tcpip-forward is client-to-server; a server must not send one. */
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
|
AssertNotNull(ctx);
|
|
ssh = wolfSSH_new(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, "0.0.0.0", 22, 1),
|
|
WS_BAD_ARGUMENT);
|
|
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, "0.0.0.0", 22, 1),
|
|
WS_BAD_ARGUMENT);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
#endif /* WOLFSSH_FWD */
|
|
|
|
static void test_wolfSSH_QueryAlgoList(void)
|
|
{
|
|
const char* name;
|
|
word32 i, j;
|
|
int k;
|
|
|
|
i = 0;
|
|
name = NULL;
|
|
do {
|
|
name = wolfSSH_QueryKex(&i);
|
|
AssertIntNE(i, 0);
|
|
} while (name != NULL);
|
|
|
|
i = 0;
|
|
name = NULL;
|
|
do {
|
|
name = wolfSSH_QueryKey(&i);
|
|
AssertIntNE(i, 0);
|
|
} while (name != NULL);
|
|
|
|
i = 0;
|
|
name = NULL;
|
|
do {
|
|
name = wolfSSH_QueryCipher(&i);
|
|
AssertIntNE(i, 0);
|
|
} while (name != NULL);
|
|
|
|
i = 0;
|
|
name = NULL;
|
|
do {
|
|
name = wolfSSH_QueryMac(&i);
|
|
AssertIntNE(i, 0);
|
|
} while (name != NULL);
|
|
|
|
/* This test case picks up where the index left off. */
|
|
j = i;
|
|
name = wolfSSH_QueryKex(&i);
|
|
AssertNull(name);
|
|
i = j;
|
|
name = wolfSSH_QueryKey(&i);
|
|
AssertNull(name);
|
|
i = j;
|
|
name = wolfSSH_QueryCipher(&i);
|
|
AssertNull(name);
|
|
i = j;
|
|
name = wolfSSH_QueryMac(&i);
|
|
AssertNull(name);
|
|
|
|
k = wolfSSH_CheckAlgoName("ssh-rsa");
|
|
#ifndef WOLFSSH_NO_SSH_RSA_SHA1
|
|
AssertIntEQ(WS_SUCCESS, k);
|
|
#else
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, k);
|
|
#endif /* WOLFSSH_NO_SSH_RSA_SHA1 */
|
|
|
|
k = wolfSSH_CheckAlgoName("ecdsa-sha2-nistp256");
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
|
AssertIntEQ(WS_SUCCESS, k);
|
|
#else
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, k);
|
|
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
|
|
|
k = wolfSSH_CheckAlgoName("diffie-hellman-group14-sha256");
|
|
#ifndef WOLFSSH_NO_DH_GROUP14_SHA256
|
|
AssertIntEQ(WS_SUCCESS, k);
|
|
#else
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, k);
|
|
#endif /* WOLFSSH_NO_DH_GROUP14_SHA256 */
|
|
|
|
k = wolfSSH_CheckAlgoName("server-sig-algs");
|
|
AssertIntEQ(WS_SUCCESS, k);
|
|
|
|
k = wolfSSH_CheckAlgoName("nistp256");
|
|
AssertIntEQ(WS_SUCCESS, k);
|
|
|
|
k = wolfSSH_CheckAlgoName("not-an-algo@wolfssl.com");
|
|
AssertIntEQ(WS_INVALID_ALGO_ID, k);
|
|
}
|
|
|
|
|
|
/* Length of the comma-separated entry starting at list. */
|
|
static word32 AlgoListEntrySz(const char* list)
|
|
{
|
|
word32 sz = 0;
|
|
|
|
while (list[sz] != '\0' && list[sz] != ',') {
|
|
sz++;
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
|
|
/* Returns 1 when name is a whole comma-separated entry of list. */
|
|
static int AlgoListHasName(const char* list, const char* name, word32 nameSz)
|
|
{
|
|
word32 entSz;
|
|
|
|
while (*list != '\0') {
|
|
entSz = AlgoListEntrySz(list);
|
|
if (entSz == nameSz && WSTRNCMP(list, name, nameSz) == 0) {
|
|
return 1;
|
|
}
|
|
list += entSz;
|
|
if (*list == ',') {
|
|
list++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* The client host-key list and the accepted list are maintained separately in
|
|
* internal.c. They must differ by exactly the OpenSSH certificate names, so a
|
|
* new algorithm cannot be added to one and forgotten in the other. */
|
|
static void test_wolfSSH_AlgoListKeyInSync(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
const char* hostList;
|
|
const char* acceptList;
|
|
const char* p;
|
|
static const char osshSuffix[] = "-cert-v01@openssh.com";
|
|
word32 suffixSz = (word32)WSTRLEN(osshSuffix);
|
|
word32 entSz;
|
|
int isOsshCert;
|
|
|
|
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
AssertNotNull(ctx);
|
|
hostList = wolfSSH_CTX_GetAlgoListKey(ctx);
|
|
acceptList = wolfSSH_CTX_GetAlgoListKeyAccepted(ctx);
|
|
AssertNotNull(hostList);
|
|
AssertNotNull(acceptList);
|
|
|
|
/* Every offered host key is accepted, and none is a certificate name. */
|
|
p = hostList;
|
|
while (*p != '\0') {
|
|
entSz = AlgoListEntrySz(p);
|
|
if (entSz > 0) {
|
|
AssertIntEQ(1, AlgoListHasName(acceptList, p, entSz));
|
|
isOsshCert = (entSz > suffixSz && WSTRNCMP(p + entSz - suffixSz,
|
|
osshSuffix, suffixSz) == 0);
|
|
AssertIntEQ(0, isOsshCert);
|
|
}
|
|
p += entSz;
|
|
if (*p == ',') {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/* Every accepted non-certificate name is offered as a host key. */
|
|
p = acceptList;
|
|
while (*p != '\0') {
|
|
entSz = AlgoListEntrySz(p);
|
|
if (entSz > 0) {
|
|
isOsshCert = (entSz > suffixSz && WSTRNCMP(p + entSz - suffixSz,
|
|
osshSuffix, suffixSz) == 0);
|
|
if (!isOsshCert) {
|
|
AssertIntEQ(1, AlgoListHasName(hostList, p, entSz));
|
|
}
|
|
}
|
|
p += entSz;
|
|
if (*p == ',') {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
|
|
#if defined(WOLFSSH_SFTP) && !defined(NO_WOLFSSH_CLIENT) && \
|
|
!defined(SINGLE_THREADED)
|
|
|
|
static byte* kbResponse = (byte*)"test";
|
|
static word32 kbResponseLength = 4;
|
|
|
|
static int keyboardUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
|
|
{
|
|
int ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
|
|
|
|
(void)ctx;
|
|
|
|
if (authType == WOLFSSH_USERAUTH_KEYBOARD) {
|
|
AssertIntEQ(1, authData->sf.keyboard.promptCount);
|
|
AssertStrEQ("KB Auth Password: ",
|
|
(const char*)authData->sf.keyboard.prompts[0]);
|
|
|
|
authData->sf.keyboard.responseCount = 1;
|
|
authData->sf.keyboard.responseLengths = &kbResponseLength;
|
|
authData->sf.keyboard.responses = (byte**)&kbResponse;
|
|
ret = WS_SUCCESS;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void keyboard_client_connect(WOLFSSH_CTX** ctx, WOLFSSH** ssh, int port)
|
|
{
|
|
SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID;
|
|
SOCKADDR_IN_T clientAddr;
|
|
socklen_t clientAddrSz = sizeof(clientAddr);
|
|
int ret;
|
|
char* host = (char*)wolfSshIp;
|
|
const char* username = "test";
|
|
|
|
if (ctx == NULL || ssh == NULL) {
|
|
return;
|
|
}
|
|
|
|
*ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
|
if (*ctx == NULL) {
|
|
return;
|
|
}
|
|
|
|
wolfSSH_CTX_SetPublicKeyCheck(*ctx, AcceptAnyServerHostKey);
|
|
wolfSSH_SetUserAuth(*ctx, keyboardUserAuth);
|
|
*ssh = wolfSSH_new(*ctx);
|
|
if (*ssh == NULL) {
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
return;
|
|
}
|
|
|
|
build_addr(&clientAddr, host, port);
|
|
tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family);
|
|
if (sockFd < 0) {
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz);
|
|
if (ret != 0){
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = wolfSSH_SetUsername(*ssh, username);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_set_fd(*ssh, (int)sockFd);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_connect(*ssh);
|
|
|
|
if (ret != WS_SUCCESS){
|
|
WCLOSESOCKET(sockFd);
|
|
wolfSSH_free(*ssh);
|
|
wolfSSH_CTX_free(*ctx);
|
|
*ctx = NULL;
|
|
*ssh = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void test_wolfSSH_KeyboardInteractive(void)
|
|
{
|
|
func_args ser;
|
|
tcp_ready ready;
|
|
int argsCount;
|
|
WS_SOCKET_T clientFd;
|
|
|
|
const char* args[10];
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
|
|
THREAD_TYPE serThread;
|
|
|
|
WMEMSET(&ser, 0, sizeof(func_args));
|
|
|
|
argsCount = 0;
|
|
args[argsCount++] = ".";
|
|
args[argsCount++] = "-1";
|
|
/* Echo mode: "test" is not an account on the host, so the echoserver's
|
|
* shell callback would refuse the shell request this client sends. */
|
|
args[argsCount++] = "-f";
|
|
args[argsCount++] = "-i";
|
|
args[argsCount++] = "test:test";
|
|
args[argsCount++] = "-p";
|
|
args[argsCount++] = "0";
|
|
ser.argv = (char**)args;
|
|
ser.argc = argsCount;
|
|
ser.signal = &ready;
|
|
InitTcpReady(ser.signal);
|
|
ThreadStart(echoserver_test, (void*)&ser, &serThread);
|
|
WaitTcpReady(&ready);
|
|
|
|
keyboard_client_connect(&ctx, &ssh, ready.port);
|
|
AssertNotNull(ctx);
|
|
AssertNotNull(ssh);
|
|
|
|
|
|
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
|
|
|
|
#if DEFAULT_HIGHWATER_MARK < 8000
|
|
if (argsCount == WS_REKEYING) {
|
|
/* in cases where highwater mark is really small a re-key could happen */
|
|
argsCount = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
AssertIntEQ(argsCount, WS_SUCCESS);
|
|
|
|
/* close client socket down */
|
|
clientFd = wolfSSH_get_fd(ssh);
|
|
WCLOSESOCKET(clientFd);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
#ifdef WOLFSSH_ZEPHYR
|
|
/* Weird deadlock without this sleep */
|
|
k_sleep(Z_TIMEOUT_TICKS(100));
|
|
#endif
|
|
ThreadJoin(serThread);
|
|
FreeTcpReady(&ready);
|
|
}
|
|
|
|
#else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
|
|
static void test_wolfSSH_KeyboardInteractive(void) { ; }
|
|
#endif /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
|
|
|
|
#ifndef NO_WOLFSSH_SERVER
|
|
|
|
/* Supplies the prompt set the test installed as the userAuth context. */
|
|
static int emptyPromptUserAuth(byte authType, WS_UserAuthData* authData,
|
|
void* ctx)
|
|
{
|
|
if (authType == WOLFSSH_USERAUTH_KEYBOARD_SETUP) {
|
|
WMEMCPY(&authData->sf.keyboard, (WS_UserAuthData_Keyboard*)ctx,
|
|
sizeof(WS_UserAuthData_Keyboard));
|
|
return WOLFSSH_USERAUTH_SUCCESS;
|
|
}
|
|
return WOLFSSH_USERAUTH_FAILURE;
|
|
}
|
|
|
|
|
|
/* The sender must refuse a setup callback that supplies an empty prompt.
|
|
* Refused before sizing, so no keyed session is needed. */
|
|
static void test_wolfSSH_KeyboardInteractive_emptyPrompt(void)
|
|
{
|
|
WOLFSSH_CTX* ctx = NULL;
|
|
WOLFSSH* ssh = NULL;
|
|
WS_UserAuthData authData;
|
|
WS_UserAuthData_Keyboard prompts;
|
|
byte* promptText[1];
|
|
word32 promptLengths[1];
|
|
byte promptEcho[1];
|
|
|
|
promptText[0] = (byte*)"Password: ";
|
|
promptLengths[0] = 10;
|
|
promptEcho[0] = 0;
|
|
WMEMSET(&prompts, 0, sizeof(prompts));
|
|
prompts.promptCount = 1;
|
|
prompts.prompts = promptText;
|
|
prompts.promptLengths = promptLengths;
|
|
prompts.promptEcho = promptEcho;
|
|
|
|
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
|
|
wolfSSH_SetUserAuth(ctx, emptyPromptUserAuth);
|
|
AssertNotNull(ssh = wolfSSH_new(ctx));
|
|
wolfSSH_SetUserAuthCtx(ssh, &prompts);
|
|
|
|
/* Control: the intact prompt set clears validation. It fails later, on
|
|
* an unkeyed session, but not as bad usage. */
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
AssertIntNE(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
|
|
|
|
/* Zero-length prompt. */
|
|
promptLengths[0] = 0;
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
|
|
|
|
/* Non-zero length, no buffer. */
|
|
promptLengths[0] = 10;
|
|
promptText[0] = NULL;
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
|
|
|
|
/* A prompt longer than the payload bound. */
|
|
promptLengths[0] = WOLFSSH_MAX_PROMPT_SZ + 1;
|
|
promptText[0] = (byte*)"Password: ";
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
|
|
|
|
/* Prompt count with the arrays unset. */
|
|
prompts.prompts = NULL;
|
|
prompts.promptLengths = NULL;
|
|
prompts.promptEcho = NULL;
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
|
|
|
|
wolfSSH_free(ssh);
|
|
wolfSSH_CTX_free(ctx);
|
|
}
|
|
|
|
#else /* NO_WOLFSSH_SERVER */
|
|
static void test_wolfSSH_KeyboardInteractive_emptyPrompt(void) { ; }
|
|
#endif /* NO_WOLFSSH_SERVER */
|
|
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
|
|
|
|
#endif /* WOLFSSH_TEST_BLOCK */
|
|
|
|
|
|
int wolfSSH_ApiTest(int argc, char** argv)
|
|
{
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
#ifdef WOLFSSH_TEST_BLOCK
|
|
return 77;
|
|
#else
|
|
WSTARTTCP();
|
|
|
|
AssertIntEQ(wolfSSH_Init(), WS_SUCCESS);
|
|
|
|
#if defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,2)
|
|
{
|
|
int i;
|
|
for (i = 0; i < FIPS_CAST_COUNT; i++) {
|
|
AssertIntEQ(wc_RunCast_fips(i), WS_SUCCESS);
|
|
}
|
|
}
|
|
#endif /* HAVE_FIPS */
|
|
|
|
test_wstrcat();
|
|
test_wolfSSH_Log_sanitize();
|
|
test_wolfSSH_CTX_new();
|
|
test_server_wolfSSH_new();
|
|
test_client_wolfSSH_new();
|
|
test_wolfSSH_set_fd();
|
|
test_wolfSSH_SetUsername();
|
|
test_wolfSSH_SetChannelType();
|
|
test_wolfSSH_ConvertConsole();
|
|
test_wolfSSH_CTX_UsePrivateKey_buffer();
|
|
test_wolfSSH_CTX_UseCert_buffer();
|
|
test_wolfSSH_CTX_UseCert_file();
|
|
test_wolfSSH_CTX_AddRootCert_file();
|
|
test_wolfSSH_CTX_AddRootCert_bundle();
|
|
test_wolfSSH_CTX_AddRootCert_file_trusted();
|
|
test_wolfSSH_ReadCert_buffer_trusted();
|
|
test_wolfSSH_ReadCert_buffer();
|
|
test_wolfSSH_ReadCert_file();
|
|
test_wolfSSH_CTX_UsePrivateKey_buffer_pem();
|
|
test_wolfSSH_CTX_SetWindowPacketSize();
|
|
test_wolfSSH_CertMan();
|
|
test_wolfSSH_ReadKey();
|
|
test_wolfSSH_ReadPublicKey_pem();
|
|
test_wolfSSH_ReadKey_badPad();
|
|
#if defined(WOLFSSH_TPM) && !defined(NO_FILESYSTEM) && \
|
|
!defined(NO_WRITE_TEMP_FILES) && !defined(WOLFSSH_USER_FILESYSTEM)
|
|
test_LoadTpmSshKey_NoTrailingNewline();
|
|
#endif
|
|
#if defined(WOLFSSH_TPM) && defined(WOLFSSH_TEST_INTERNAL)
|
|
test_GetOpenSshPublicKey_type();
|
|
#endif
|
|
test_wolfSSH_ReadKey_shortBuffer();
|
|
test_wolfSSH_ReadKey_noTrailingNewline();
|
|
test_wolfSSH_ReadKey_sshNoComment();
|
|
test_wolfSSH_QueryAlgoList();
|
|
test_wolfSSH_SetMaxAuthAttempts();
|
|
test_wolfSSH_AlgoListKeyInSync();
|
|
test_wolfSSH_SetAlgoList();
|
|
test_wolfSSH_CheckAlgoList();
|
|
#ifdef WOLFSSH_FWD
|
|
test_wolfSSH_FwdRemote_badArgs();
|
|
#endif
|
|
#ifdef WOLFSSH_AGENT
|
|
test_wolfSSH_agent_signrequest_partial_write();
|
|
test_wolfSSH_agent_signrequest_wrong_message();
|
|
test_wolfSSH_agent_signrequest_signature_too_large();
|
|
test_wolfSSH_agent_signrequest_success();
|
|
test_wolfSSH_agent_signrequest_large_response();
|
|
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
|
test_wolfSSH_agent_signrequest_oversize_rsa_key();
|
|
test_wolfSSH_agent_signrequest_rsa_3072();
|
|
#endif
|
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
|
|
test_wolfSSH_agent_signrequest_ecc_p521();
|
|
#endif
|
|
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
|
test_wolfSSH_agent_signrequest_rsa_no_modulus();
|
|
#if defined(RSA_MAX_SIZE) && (((RSA_MAX_SIZE / 8) + 64) < AGENT_TEST_BUF_SZ)
|
|
test_wolfSSH_agent_signrequest_rsa_too_large();
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#ifdef WOLFSSH_OSSH_CERTS
|
|
#ifndef WOLFSSH_NO_ED25519
|
|
test_wolfSSH_ReadCert_buffer_ossh();
|
|
test_wolfSSH_ReadCert_file_ossh();
|
|
test_wolfSSH_OsshCert_valid();
|
|
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
|
|
test_wolfSSH_OsshCert_ecc_curve_mismatch();
|
|
#endif
|
|
test_wolfSSH_OsshCert_checktype();
|
|
test_wolfSSH_OsshCert_malformed();
|
|
#endif
|
|
test_wolfSSH_OsshCert_options();
|
|
test_wolfSSH_OsshCert_baseid();
|
|
test_wolfSSH_OsshCert_rsasigid();
|
|
#endif
|
|
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
|
|
test_wolfSSH_KeyboardInteractive();
|
|
test_wolfSSH_KeyboardInteractive_emptyPrompt();
|
|
#endif
|
|
|
|
/* SCP tests */
|
|
test_wolfSSH_SCP_CB();
|
|
test_wolfSSH_SCP_SendSymlinkReject();
|
|
test_wolfSSH_SCP_SendRecursiveEntry();
|
|
test_wolfSSH_SCP_ReKey();
|
|
test_wolfSSH_SCP_ReKey_NonBlock();
|
|
test_wolfSSH_SCP_ReKey_ToServer();
|
|
test_wolfSSH_SCP_ReKey_ToServer_NonBlock();
|
|
test_wolfSSH_SCP_SendZeroFirst();
|
|
test_wolfSSH_SCP_RecursiveTwoFiles();
|
|
|
|
/* SFTP tests */
|
|
test_wolfSSH_SFTP_SendReadPacket();
|
|
test_wolfSSH_SFTP_PartialSend();
|
|
test_wolfSSH_SFTP_ReKey();
|
|
test_wolfSSH_SFTP_ReKey_NonBlock();
|
|
test_wolfSSH_SFTP_Confinement();
|
|
test_wolfSSH_SFTP_StartPathNotConfined();
|
|
test_wolfSSH_SFTP_SetConfinePath();
|
|
test_wolfSSH_SFTP_SetDefaultPath();
|
|
test_wolfSSH_SFTP_SaveOfst();
|
|
test_wolfSSH_SFTP_PutResume();
|
|
test_wolfSSH_SFTP_GetResume();
|
|
|
|
/* Either SCP or SFTP */
|
|
test_wolfSSH_RealPath();
|
|
AssertIntEQ(wolfSSH_Cleanup(), WS_SUCCESS);
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifndef NO_APITEST_MAIN_DRIVER
|
|
int main(int argc, char** argv)
|
|
{
|
|
return wolfSSH_ApiTest(argc, argv);
|
|
}
|
|
#endif
|