wolfssh/src/ssh.c

6164 lines
182 KiB
C

/* ssh.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/>.
*/
/*
* The ssh module contains the public API for wolfSSH.
*/
/* CompareStringOrdinal() and friends need a Vista-or-later SDK profile;
* mingw-w64 has historically defaulted _WIN32_WINNT to pre-Vista, so raise
* the floor before the first header that pulls in <windows.h> (wolfssh/ssh.h
* via port.h does). The undefined case is raised only under mingw: MSVC's
* SDK defaults an undefined _WIN32_WINNT to its newest profile, which a
* 0x0600 define here would silently lower. An explicit pre-Vista target is
* raised on any compiler since this file cannot build against it. WINVER is
* pinned alongside so the two profiles cannot disagree. Keyed on _WIN32
* rather than WOLFSSH_WINDOWS_CERT_STORE: this is the only macro guaranteed
* defined this early, since a user_settings.h build defines
* WOLFSSH_WINDOWS_CERT_STORE only once <wolfssh/ssh.h> pulls in settings.h
* below, after <windows.h> has already been seen. Harmless for
* non-cert-store Windows builds. */
#if defined(_WIN32) && \
((defined(__MINGW32__) && !defined(_WIN32_WINNT)) || \
(defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600))
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#undef WINVER
#define WINVER 0x0600
#endif
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <wolfssh/ssh.h>
#include <wolfssh/internal.h>
#include <wolfssh/log.h>
#include <wolfssl/wolfcrypt/wc_port.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/random.h>
#ifdef WOLFSSH_WINDOWS_CERT_STORE
#include <windows.h>
#include <wincrypt.h>
#include <ncrypt.h>
#include <string.h>
#include <wchar.h>
/* Fallbacks for SDKs that predate these wincrypt.h/ncrypt.h
* definitions. The values must match the SDK headers exactly. The
* CERT_SYSTEM_STORE_* location constants are consumed only by
* src/certman.c, which carries its own fallbacks. */
#ifndef CERT_NCRYPT_KEY_SPEC
#define CERT_NCRYPT_KEY_SPEC 0xFFFFFFFF
#endif
#ifndef CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG
#define CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG 0x00040000
#endif
#ifndef NCRYPT_KEY_USAGE_PROPERTY
#define NCRYPT_KEY_USAGE_PROPERTY L"Key Usage"
#endif
#ifndef NCRYPT_ALLOW_DECRYPT_FLAG
#define NCRYPT_ALLOW_DECRYPT_FLAG 0x00000001
#endif
#ifndef NCRYPT_ALLOW_SIGNING_FLAG
#define NCRYPT_ALLOW_SIGNING_FLAG 0x00000002
#endif
#endif /* WOLFSSH_WINDOWS_CERT_STORE */
#ifdef NO_INLINE
#include <wolfssh/misc.h>
#else
#define WOLFSSH_MISC_INCLUDED
#if defined(WOLFSSL_NUCLEUS)
#include "src/wolfssh_misc.c"
#else
#include "src/misc.c"
#endif
#endif
#ifdef HAVE_FIPS
#include <wolfssl/wolfcrypt/fips_test.h>
static void myFipsCb(int ok, int err, const char* hash)
{
printf("in my Fips callback, ok = %d, err = %d\n", ok, err);
printf("message = %s\n", wc_GetErrorString(err));
printf("hash = %s\n", hash);
if (err == IN_CORE_FIPS_E) {
printf("In core integrity hash check failure, copy above hash\n");
printf("into verifyCore[] in fips_test.c and rebuild\n");
}
}
#endif /* HAVE_FIPS */
int wolfSSH_Init(void)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_Init()");
if (wolfCrypt_Init() != 0)
ret = WS_CRYPTO_FAILED;
#ifdef HAVE_FIPS
wolfCrypt_SetCb_fips(myFipsCb);
#endif
#if defined(WC_RNG_SEED_CB) && defined(HAVE_HASHDRBG)
wc_SetSeed_Cb(wc_GenerateSeed);
#endif
#if !defined(NO_FILESYSTEM) && defined(WOLFSSH_ZEPHYR) && \
(defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP))
if (wssh_z_fds_init() != 0)
ret = WS_CRYPTO_FAILED;
#endif
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_Init(), returning %d", ret);
return ret;
}
int wolfSSH_Cleanup(void)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_Cleanup()");
if (wolfCrypt_Cleanup() != 0)
ret = WS_CRYPTO_FAILED;
#if !defined(NO_FILESYSTEM) && defined(WOLFSSH_ZEPHYR) && \
(defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP))
if (wssh_z_fds_cleanup() != 0)
ret = WS_CRYPTO_FAILED;
#endif
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_Cleanup(), returning %d", ret);
return ret;
}
WOLFSSH_CTX* wolfSSH_CTX_new(byte side, void* heap)
{
WOLFSSH_CTX* ctx;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_new()");
if (side != WOLFSSH_ENDPOINT_SERVER && side != WOLFSSH_ENDPOINT_CLIENT) {
WLOG(WS_LOG_DEBUG, "Invalid endpoint type");
return NULL;
}
ctx = (WOLFSSH_CTX*)WMALLOC(sizeof(WOLFSSH_CTX), heap, DYNTYPE_CTX);
if (CtxInit(ctx, side, heap) == NULL) {
WFREE(ctx, heap, DYNTYPE_CTX);
ctx = NULL;
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_CTX_new(), ctx = %p", ctx);
return ctx;
}
void wolfSSH_CTX_free(WOLFSSH_CTX* ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_free()");
if (ctx) {
CtxResourceFree(ctx);
WFREE(ctx, ctx->heap, DYNTYPE_CTX);
}
}
WOLFSSH* wolfSSH_new(WOLFSSH_CTX* ctx)
{
WOLFSSH* ssh;
void* heap = NULL;
WOLFSSH_UNUSED(heap);
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_new()");
if (ctx)
heap = ctx->heap;
else {
WLOG(WS_LOG_ERROR, "Trying to init a wolfSSH w/o wolfSSH_CTX");
return NULL;
}
ssh = (WOLFSSH*)WMALLOC(sizeof(WOLFSSH), heap, DYNTYPE_SSH);
ssh = SshInit(ssh, ctx);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_new(), ssh = %p", ssh);
return ssh;
}
void wolfSSH_free(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_free()");
if (ssh) {
void* heap = ssh->ctx ? ssh->ctx->heap : NULL;
#ifdef WOLFSSH_SFTP
if (wolfSSH_SFTP_free(ssh) != WS_SUCCESS) {
WLOG(WS_LOG_SFTP, "Error cleaning up SFTP connection");
}
#endif
#ifdef WOLFSSH_AGENT
if (ssh->agent != NULL)
wolfSSH_AGENT_free(ssh->agent);
#endif /* WOLFSSH_AGENT */
SshResourceFree(ssh, heap);
WFREE(ssh, heap, DYNTYPE_SSH);
}
}
int wolfSSH_set_fd(WOLFSSH* ssh, WS_SOCKET_T fd)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_set_fd()");
if (ssh) {
ssh->rfd = fd;
ssh->wfd = fd;
ssh->ioReadCtx = &ssh->rfd;
ssh->ioWriteCtx = &ssh->wfd;
return WS_SUCCESS;
}
return WS_BAD_ARGUMENT;
}
WS_SOCKET_T wolfSSH_get_fd(const WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_get_fd()");
if (ssh)
return ssh->rfd;
/* Return the same invalid-socket sentinel wolfSSH_new() initializes
* rfd/wfd to, rather than an error enum, so callers get a value of the
* socket type they can compare against. */
#ifdef USE_WINDOWS_API
return INVALID_SOCKET;
#else
return -1;
#endif
}
int wolfSSH_SetFilesystemHandle(WOLFSSH* ssh, void* handle)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetFilesystemHandle()");
if (ssh) {
ssh->fs = handle;
return WS_SUCCESS;
}
return WS_BAD_ARGUMENT;
}
void* wolfSSH_GetFilesystemHandle(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetFilesystemHandle()");
if (ssh)
return ssh->fs;
return NULL;
}
int wolfSSH_SetHighwater(WOLFSSH* ssh, word32 level)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetHighwater()");
if (ssh) {
ssh->highwaterMark = level;
return WS_SUCCESS;
}
return WS_BAD_ARGUMENT;
}
word32 wolfSSH_GetHighwater(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetHighwater()");
if (ssh)
return ssh->highwaterMark;
return 0;
}
void wolfSSH_CTX_SetMsgHighwater(WOLFSSH_CTX* ctx, word32 level)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetMsgHighwater()");
if (ctx)
ctx->msgHighwaterMark = level;
}
void wolfSSH_SetMsgHighwater(WOLFSSH* ssh, word32 level)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetMsgHighwater()");
if (ssh)
ssh->msgHighwaterMark = level;
}
word32 wolfSSH_GetMsgHighwater(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetMsgHighwater()");
if (ssh)
return ssh->msgHighwaterMark;
return 0;
}
void wolfSSH_SetHighwaterCb(WOLFSSH_CTX* ctx, word32 level,
WS_CallbackHighwater cb)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetHighwaterCb()");
if (ctx) {
ctx->highwaterMark = level;
ctx->highwaterCb = cb;
}
}
void wolfSSH_SetHighwaterCtx(WOLFSSH* ssh, void* ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetHighwaterCtx()");
if (ssh)
ssh->highwaterCtx = ctx;
}
void* wolfSSH_GetHighwaterCtx(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetHighwaterCtx()");
if (ssh)
return ssh->highwaterCtx;
return NULL;
}
void wolfSSH_SetGlobalReq(WOLFSSH_CTX *ctx, WS_CallbackGlobalReq cb)
{
if (ctx)
ctx->globalReqCb = cb;
}
void wolfSSH_SetReqSuccess(WOLFSSH_CTX *ctx, WS_CallbackReqSuccess cb)
{
if (ctx)
ctx->reqSuccessCb = cb;
}
void wolfSSH_SetReqFailure(WOLFSSH_CTX *ctx, WS_CallbackReqSuccess cb)
{
if (ctx)
ctx->reqFailureCb = cb;
}
int wolfSSH_CTX_SetGlobalReqAnyCb(WOLFSSH_CTX* ctx, WS_CallbackGlobalReqAny cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->globalReqAnyCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
void wolfSSH_SetGlobalReqCtx(WOLFSSH* ssh, void *ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetGlobalReqCtx()");
if (ssh)
ssh->globalReqCtx = ctx;
}
void *wolfSSH_GetGlobalReqCtx(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetGlobalReqCtx()");
if (ssh)
return ssh->globalReqCtx;
return NULL;
}
void wolfSSH_SetReqSuccessCtx(WOLFSSH *ssh, void *ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetReqSuccessCtx()");
if (ssh)
ssh->reqSuccessCtx = ctx;
}
void *wolfSSH_GetReqSuccessCtx(WOLFSSH *ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetReqSuccessCtx()");
if (ssh)
return ssh->reqSuccessCtx;
return NULL;
}
void wolfSSH_SetReqFailureCtx(WOLFSSH *ssh, void *ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetReqFailureCtx()");
if (ssh)
ssh->reqFailureCtx = ctx;
}
void *wolfSSH_GetReqFailureCtx(WOLFSSH *ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetReqFailureCtx()");
if (ssh)
return ssh->reqFailureCtx;
return NULL;
}
int wolfSSH_get_error(const WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_get_error()");
if (ssh)
return ssh->error;
return WS_SSH_NULL_E;
}
const char* wolfSSH_get_error_name(const WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_get_error_name()");
if (ssh)
return GetErrorString(ssh->error);
return GetErrorString(WS_SSH_NULL_E);
}
const char* wolfSSH_ErrorToName(int err)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ErrorToName()");
return GetErrorString(err);
}
#ifdef WOLFSSH_TPM
void wolfSSH_SetTpmDev(WOLFSSH* ssh, WOLFTPM2_DEV* dev)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmDev()");
if (ssh && ssh->ctx) {
ssh->ctx->tpmDev = dev;
if (ssh->ctx->tpmDev == NULL) {
WLOG(WS_LOG_DEBUG, "wolfSSH_SetTpmDev: Set tpm dev failed");
}
}
}
void wolfSSH_SetTpmKey(WOLFSSH* ssh, WOLFTPM2_KEY* key)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmKey()");
if (ssh && ssh->ctx) {
ssh->ctx->tpmKey = key;
if (ssh->ctx->tpmKey == NULL) {
WLOG(WS_LOG_DEBUG, "wolfSSH_SetTpmKey: Set tpm key failed");
}
}
}
void* wolfSSH_GetTpmDev(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmDev()");
if (ssh && ssh->ctx) {
return ssh->ctx->tpmDev;
}
return NULL;
}
void* wolfSSH_GetTpmKey(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmKey()");
if (ssh && ssh->ctx) {
return ssh->ctx->tpmKey;
}
return NULL;
}
int wolfSSH_CTX_UseTpmHostKey(WOLFSSH_CTX* ctx,
WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key)
{
int ret = WS_SUCCESS;
byte keyId = ID_NONE;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UseTpmHostKey()");
if (ctx == NULL || dev == NULL || key == NULL) {
ret = WS_BAD_ARGUMENT;
}
/* Only one TPM host key is supported per context (single ctx->tpmKey). */
if (ret == WS_SUCCESS && ctx->tpmKey != NULL && ctx->tpmKey != key) {
WLOG(WS_LOG_DEBUG,
"wolfSSH_CTX_UseTpmHostKey: a TPM host key is already set");
ret = WS_BAD_ARGUMENT;
}
if (ret == WS_SUCCESS) {
if (key->pub.publicArea.type == TPM_ALG_ECC) {
switch (key->pub.publicArea.parameters.eccDetail.curveID) {
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
case TPM_ECC_NIST_P256:
keyId = ID_ECDSA_SHA2_NISTP256;
break;
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384
case TPM_ECC_NIST_P384:
keyId = ID_ECDSA_SHA2_NISTP384;
break;
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
case TPM_ECC_NIST_P521:
keyId = ID_ECDSA_SHA2_NISTP521;
break;
#endif
default:
ret = WS_INVALID_PRIME_CURVE;
}
}
else if (key->pub.publicArea.type == TPM_ALG_RSA) {
#if !defined(WOLFSSH_NO_RSA) && \
(!defined(WOLFSSH_NO_RSA_SHA2_256) || \
!defined(WOLFSSH_NO_RSA_SHA2_512) || \
(defined(WOLFSSH_NO_SHA1_SOFT_DISABLE) && \
!defined(WOLFSSH_NO_SSH_RSA_SHA1)))
keyId = ID_SSH_RSA;
#else
ret = WS_INVALID_ALGO_ID;
#endif
}
else {
ret = WS_INVALID_ALGO_ID;
}
}
if (ret == WS_SUCCESS) {
ctx->tpmDev = dev;
ctx->tpmKey = key;
ret = wolfSSH_SetHostTpmKey(ctx, keyId);
}
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_CTX_UseTpmHostKey(), ret = %d", ret);
return ret;
}
#endif /* WOLFSSH_TPM */
#if !defined(NO_WOLFSSH_SERVER) || !defined(NO_WOLFSSH_CLIENT)
/* A peer's CHANNEL_EOF is legal once its channel is open, RFC 4254 section
* 5.3, not a handshake failure. */
static int DoReceiveHandshake(WOLFSSH* ssh)
{
int ret = DoReceive(ssh);
if (ret == WS_EOF)
ret = WS_SUCCESS;
return ret;
}
#endif /* !NO_WOLFSSH_SERVER || !NO_WOLFSSH_CLIENT */
#ifndef NO_WOLFSSH_SERVER
const char acceptError[] = "accept error: %s, %d";
const char acceptState[] = "accept state: %s";
int wolfSSH_accept(WOLFSSH* ssh)
{
byte stopState;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_accept()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
/* No handshake on a session that is over. The pending-send block below
* would flush a queued disconnect as the next handshake message. */
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
/* clear want read/writes for retry */
if (ssh->error == WS_WANT_READ || ssh->error == WS_WANT_WRITE || ssh->error == WS_AUTH_PENDING)
ssh->error = 0;
if (ssh->error != 0) {
WLOG(WS_LOG_DEBUG, "Calling wolfSSH_accept in error state");
return WS_INVALID_STATE_E;
}
/* In application-driven mode the state machine stops as soon as the
* user is authenticated; everything past that is the application's.
* Only stop there if the session has not already gone by: the loop
* below tests the stop state exactly, so a state it has stepped over
* would never terminate it. */
stopState = (ssh->appChannels
&& ssh->acceptState <= ACCEPT_SERVER_USERAUTH_SENT) ?
ACCEPT_SERVER_USERAUTH_SENT : ACCEPT_CLIENT_SESSION_ESTABLISHED;
/* check if data pending to be sent */
if (ssh->outputBuffer.length > 0 &&
ssh->acceptState < ACCEPT_CLIENT_SESSION_ESTABLISHED) {
if ((ssh->error = wolfSSH_SendPacket(ssh)) == WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Sent pending packet");
/* adjust state, a couple of them use multiple sends */
if (ssh->acceptState != ACCEPT_SERVER_VERSION_SENT &&
ssh->acceptState != ACCEPT_SERVER_USERAUTH_ACCEPT_SENT &&
ssh->acceptState != ACCEPT_SERVER_KEXINIT_SENT &&
ssh->acceptState != ACCEPT_KEYED &&
ssh->acceptState != ACCEPT_SERVER_CHANNEL_ACCEPT_SENT &&
/* Never step over where this call is meant to stop. The
* loop below tests for that state exactly, and the SCP and
* SFTP re-entry states sort after it. */
ssh->acceptState != stopState) {
WLOG(WS_LOG_DEBUG, "Advancing accept state");
ssh->acceptState++;
}
/* handle in process reply state */
if (ssh->processReplyState == PROCESS_PACKET) {
WLOG(WS_LOG_DEBUG, "PR3: peerMacSz = %u", ssh->peerMacSz);
ssh->inputBuffer.idx += ssh->peerMacSz;
WLOG(WS_LOG_DEBUG, "PR4: Shrinking input buffer");
ShrinkBuffer(&ssh->inputBuffer, 1);
ssh->processReplyState = PROCESS_INIT;
WLOG(WS_LOG_DEBUG, "PR5: txCount = %u, rxCount = %u",
ssh->txCount, ssh->rxCount);
}
}
else {
return WS_FATAL_ERROR;
}
}
while (ssh->acceptState != stopState) {
switch (ssh->acceptState) {
case ACCEPT_BEGIN:
if ( (ssh->error = SendProtoId(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError, "BEGIN", ssh->error);
return WS_FATAL_ERROR;
}
ssh->acceptState = ACCEPT_SERVER_VERSION_SENT;
WLOG(WS_LOG_DEBUG, acceptState, "SERVER_VERSION_SENT");
FALL_THROUGH;
case ACCEPT_SERVER_VERSION_SENT:
while (ssh->clientState < CLIENT_VERSION_DONE) {
if ( (ssh->error = DoProtoId(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_VERSION_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->acceptState = ACCEPT_CLIENT_VERSION_DONE;
WLOG(WS_LOG_DEBUG, acceptState, "CLIENT_VERSION_DONE");
FALL_THROUGH;
case ACCEPT_CLIENT_VERSION_DONE:
if ( (ssh->error = SendKexInit(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError,
"CLIENT_VERSION_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->acceptState = ACCEPT_SERVER_KEXINIT_SENT;
WLOG(WS_LOG_DEBUG, acceptState, "SERVER_KEXINIT_SENT");
FALL_THROUGH;
case ACCEPT_SERVER_KEXINIT_SENT:
while (ssh->isKeying) {
if (DoReceive(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_KEXINIT_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->acceptState = ACCEPT_KEYED;
WLOG(WS_LOG_DEBUG, acceptState, "KEYED");
FALL_THROUGH;
case ACCEPT_KEYED:
while (ssh->clientState < CLIENT_USERAUTH_REQUEST_DONE) {
if (DoReceive(ssh) < 0) {
WLOG(WS_LOG_DEBUG, acceptError,
"KEYED", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->acceptState = ACCEPT_CLIENT_USERAUTH_REQUEST_DONE;
WLOG(WS_LOG_DEBUG, acceptState, "CLIENT_USERAUTH_REQUEST_DONE");
FALL_THROUGH;
case ACCEPT_CLIENT_USERAUTH_REQUEST_DONE:
if ( (ssh->error = SendServiceAccept(ssh,
ID_SERVICE_USERAUTH)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError,
"CLIENT_USERAUTH_REQUEST_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->acceptState = ACCEPT_SERVER_USERAUTH_ACCEPT_SENT;
WLOG(WS_LOG_DEBUG, acceptState,
"ACCEPT_SERVER_USERAUTH_ACCEPT_SENT");
FALL_THROUGH;
case ACCEPT_SERVER_USERAUTH_ACCEPT_SENT:
while (ssh->clientState < CLIENT_USERAUTH_DONE) {
if (DoReceive(ssh) < 0) {
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_ACCEPT_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->acceptState = ACCEPT_CLIENT_USERAUTH_DONE;
WLOG(WS_LOG_DEBUG, acceptState, "CLIENT_USERAUTH_DONE");
FALL_THROUGH;
case ACCEPT_CLIENT_USERAUTH_DONE:
if ( (ssh->error = SendUserAuthSuccess(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, acceptError,
"CLIENT_USERAUTH_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
WLOG(WS_LOG_DEBUG, acceptState, "SERVER_USERAUTH_SENT");
if (stopState == ACCEPT_SERVER_USERAUTH_SENT) {
/* The application takes it from here. Tested through
* stopState so a callback that changed the flag during
* this call cannot half-apply it. */
break;
}
FALL_THROUGH;
case ACCEPT_SERVER_USERAUTH_SENT:
while (ssh->clientState < CLIENT_CHANNEL_OPEN_DONE) {
if (DoReceive(ssh) < 0) {
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->acceptState = ACCEPT_SERVER_CHANNEL_ACCEPT_SENT;
WLOG(WS_LOG_DEBUG, acceptState, "SERVER_CHANNEL_ACCEPT_SENT");
FALL_THROUGH;
case ACCEPT_SERVER_CHANNEL_ACCEPT_SENT:
while (ssh->clientState < CLIENT_DONE) {
if (DoReceiveHandshake(ssh) < 0) {
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_CHANNEL_ACCEPT_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
/* Divert only into a granted session; a refusal puts
* the type and command back. */
#ifdef WOLFSSH_SCP
if (ssh->channelList != NULL
&& ssh->channelList->sessionGranted
&& wolfSSH_ChannelCommandIsScp(ssh->channelList)
== 1) {
ssh->acceptState = ACCEPT_INIT_SCP_TRANSFER;
WLOG(WS_LOG_DEBUG, acceptState, "ACCEPT_INIT_SCP_TRANSFER");
return WS_SCP_INIT;
}
#endif
#if defined(WOLFSSH_SFTP) && !defined(NO_WOLFSSH_SERVER)
{
const char* cmd = wolfSSH_GetSessionCommand(ssh);
if (cmd != NULL
&& ssh->channelList->sessionGranted
&& WOLFSSH_SESSION_SUBSYSTEM
== wolfSSH_GetSessionType(ssh)
&& wolfSSH_GetSessionCommandSz(ssh)
== (word32)WSTRLEN("sftp")
&& (WSTRCMP(cmd, "sftp") == 0)) {
ssh->acceptState = ACCEPT_INIT_SFTP;
return wolfSSH_SFTP_accept(ssh);
}
}
#endif /* WOLFSSH_SFTP and !NO_WOLFSSH_SERVER */
#ifdef WOLFSSH_AGENT
if (ssh->useAgent) {
int agentRet = wolfSSH_AGENT_ChannelOpen(ssh);
if (agentRet < WS_SUCCESS) {
/* WS_FATAL_ERROR is the disconnect, which already
* recorded WS_DISCONNECT; keep that. */
if (agentRet != WS_FATAL_ERROR)
ssh->error = agentRet;
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_FATAL_ERROR;
}
}
#endif /* WOLFSSH_AGENT */
ssh->acceptState = ACCEPT_CLIENT_SESSION_ESTABLISHED;
WLOG(WS_LOG_DEBUG, acceptState, "CLIENT_SESSION_ESTABLISHED");
break;
#ifdef WOLFSSH_SCP
case ACCEPT_INIT_SCP_TRANSFER:
if (DoScpRequest(ssh) < 0) {
WLOG(WS_LOG_DEBUG, acceptError, "INIT_SCP_TRANSFER",
ssh->error);
return WS_FATAL_ERROR;
}
return WS_SCP_COMPLETE;
#endif
#ifdef WOLFSSH_SFTP
case ACCEPT_INIT_SFTP:
return wolfSSH_SFTP_accept(ssh);
#endif
}
} /* end while */
return WS_SUCCESS;
}
#endif /* NO_WOLFSSH_SERVER */
#ifndef NO_WOLFSSH_CLIENT
const char connectError[] = "connect error: %s, %d";
const char connectState[] = "connect state: %s";
int wolfSSH_connect(WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_connect()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
/* See wolfSSH_accept(). No error-state test here, so the peer's
* disconnect reaches the state machine like a local one. */
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
/* check if data pending to be sent */
if (ssh->outputBuffer.length > 0 &&
ssh->connectState < CONNECT_SERVER_CHANNEL_REQUEST_DONE) {
if ((ssh->error = wolfSSH_SendPacket(ssh)) == WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Sent pending packet");
/* adjust state, a couple of them use multiple sends */
if (ssh->connectState != CONNECT_CLIENT_VERSION_SENT &&
ssh->connectState != CONNECT_CLIENT_KEXINIT_SENT &&
ssh->connectState != CONNECT_CLIENT_KEXDH_INIT_SENT &&
ssh->connectState != CONNECT_CLIENT_USERAUTH_REQUEST_SENT &&
ssh->connectState != CONNECT_CLIENT_USERAUTH_SENT &&
ssh->connectState != CONNECT_CLIENT_CHANNEL_OPEN_SESSION_SENT &&
ssh->connectState != CONNECT_CLIENT_CHANNEL_REQUEST_SENT) {
WLOG(WS_LOG_DEBUG, "Advancing connect state");
ssh->connectState++;
}
/* handle in process reply state */
if (ssh->processReplyState == PROCESS_PACKET) {
WLOG(WS_LOG_DEBUG, "PR3: peerMacSz = %u", ssh->peerMacSz);
ssh->inputBuffer.idx += ssh->peerMacSz;
WLOG(WS_LOG_DEBUG, "PR4: Shrinking input buffer");
ShrinkBuffer(&ssh->inputBuffer, 1);
ssh->processReplyState = PROCESS_INIT;
WLOG(WS_LOG_DEBUG, "PR5: txCount = %u, rxCount = %u",
ssh->txCount, ssh->rxCount);
}
}
else {
return WS_FATAL_ERROR;
}
}
switch (ssh->connectState) {
case CONNECT_BEGIN:
if ( (ssh->error = SendProtoId(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError, "BEGIN", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_VERSION_SENT;
WLOG(WS_LOG_DEBUG, connectState, "CLIENT_VERSION_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_VERSION_SENT:
while (ssh->serverState < SERVER_VERSION_DONE) {
if ( (ssh->error = DoProtoId(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_VERSION_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_VERSION_DONE;
WLOG(WS_LOG_DEBUG, connectState, "SERVER_VERSION_DONE");
FALL_THROUGH;
case CONNECT_SERVER_VERSION_DONE:
if ( (ssh->error = SendKexInit(ssh)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_VERSION_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_KEXINIT_SENT;
WLOG(WS_LOG_DEBUG, connectState, "CLIENT_KEXINIT_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_KEXINIT_SENT:
while (ssh->serverState < SERVER_KEXINIT_DONE) {
if (DoReceive(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_KEXINIT_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_KEXINIT_DONE;
WLOG(WS_LOG_DEBUG, connectState, "SERVER_KEXINIT_DONE");
FALL_THROUGH;
case CONNECT_SERVER_KEXINIT_DONE:
if (ssh->handshake == NULL) {
return WS_FATAL_ERROR;
}
if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {
#if !defined(WOLFSSH_NO_DH) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
ssh->error = SendKexDhGexRequest(ssh);
#endif
}
else
ssh->error = SendKexDhInit(ssh);
if (ssh->error < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_KEXINIT_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_KEXDH_INIT_SENT;
WLOG(WS_LOG_DEBUG, connectState, "CLIENT_KEXDH_INIT_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_KEXDH_INIT_SENT:
while (ssh->isKeying) {
if (DoReceive(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_KEXDH_INIT_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_KEYED;
WLOG(WS_LOG_DEBUG, connectState, "KEYED");
FALL_THROUGH;
case CONNECT_KEYED:
if ( (ssh->error = SendServiceRequest(ssh, ID_SERVICE_USERAUTH)) <
WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError, "KEYED", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
WLOG(WS_LOG_DEBUG, connectState, "CLIENT_USERAUTH_REQUEST_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_USERAUTH_REQUEST_SENT:
while (ssh->serverState < SERVER_USERAUTH_REQUEST_DONE) {
if (DoReceive(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_USERAUTH_REQUEST_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_USERAUTH_REQUEST_DONE;
WLOG(WS_LOG_DEBUG, connectState, "SERVER_USERAUTH_REQUEST_DONE");
FALL_THROUGH;
case CONNECT_SERVER_USERAUTH_REQUEST_DONE:
#ifdef WOLFSSH_AGENT
if (ssh->agentEnabled) {
ssh->agent = wolfSSH_AGENT_new(ssh->ctx->heap);
if (ssh->agent == NULL) {
ssh->agentEnabled = 0;
WLOG(WS_LOG_INFO, "Unable to create agent. Disabling.");
}
}
#endif
if ( (ssh->error = SendUserAuthRequest(ssh, ID_NONE, 0)) <
WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_USERAUTH_REQUEST_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_USERAUTH_SENT;
WLOG(WS_LOG_DEBUG, connectState, "CLIENT_USERAUTH_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_USERAUTH_SENT:
while (ssh->serverState < SERVER_USERAUTH_ACCEPT_DONE) {
if (DoReceive(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_USERAUTH_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
WLOG(WS_LOG_DEBUG, connectState, "SERVER_USERAUTH_ACCEPT_DONE");
FALL_THROUGH;
case CONNECT_SERVER_USERAUTH_ACCEPT_DONE:
{
WOLFSSH_CHANNEL* newChannel;
newChannel = ChannelNew(ssh, ID_CHANTYPE_SESSION,
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
if (newChannel == NULL) {
ssh->error = WS_MEMORY_E;
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_FATAL_ERROR;
}
if ( (ssh->error =
SendChannelOpenSession(ssh, newChannel)) < WS_SUCCESS) {
if (ssh->error == WS_WANT_WRITE ||
ssh->error == WS_WANT_READ) {
ChannelAppend(ssh, newChannel);
}
else {
ChannelDelete(newChannel, ssh->ctx->heap);
}
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ChannelAppend(ssh, newChannel);
}
ssh->connectState = CONNECT_CLIENT_CHANNEL_OPEN_SESSION_SENT;
WLOG(WS_LOG_DEBUG, connectState,
"CLIENT_CHANNEL_OPEN_SESSION_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_CHANNEL_OPEN_SESSION_SENT:
while (ssh->serverState < SERVER_CHANNEL_OPEN_DONE) {
if (DoReceiveHandshake(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_CHANNEL_OPEN_SESSION_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_CHANNEL_OPEN_SESSION_DONE;
WLOG(WS_LOG_DEBUG, connectState,
"SERVER_CHANNEL_OPEN_SESSION_DONE");
FALL_THROUGH;
case CONNECT_SERVER_CHANNEL_OPEN_SESSION_DONE:
#ifdef WOLFSSH_AGENT
if (ssh->agentEnabled) {
if ( (ssh->error = SendChannelAgentRequest(ssh))
< WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_CHANNEL_OPEN_SESSION_DONE", ssh->error);
return WS_FATAL_ERROR;
}
}
#endif
WLOG(WS_LOG_DEBUG, connectState,
"CLIENT_CHANNEL_AGENT_REQUEST_SENT");
ssh->connectState = CONNECT_CLIENT_CHANNEL_AGENT_REQUEST_SENT;
FALL_THROUGH;
case CONNECT_CLIENT_CHANNEL_AGENT_REQUEST_SENT:
#if defined(WOLFSSH_TERM) && !defined(NO_FILESYSTEM)
if (ssh->sendTerminalRequest) {
if ( (ssh->error = SendChannelTerminalRequest(ssh))
< WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_CHANNEL_AGENT_REQUEST_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
#endif
WLOG(WS_LOG_DEBUG, connectState,
"CLIENT_CHANNEL_TERMINAL_REQUEST_SENT");
ssh->connectState = CONNECT_CLIENT_CHANNEL_TERMINAL_REQUEST_SENT;
FALL_THROUGH;
case CONNECT_CLIENT_CHANNEL_TERMINAL_REQUEST_SENT:
if ( (ssh->error = SendChannelRequest(ssh, ssh->channelName,
ssh->channelNameSz)) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"SERVER_CHANNEL_OPEN_SESSION_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ssh->connectState = CONNECT_CLIENT_CHANNEL_REQUEST_SENT;
WLOG(WS_LOG_DEBUG, connectState,
"CLIENT_CHANNEL_REQUEST_SENT");
FALL_THROUGH;
case CONNECT_CLIENT_CHANNEL_REQUEST_SENT:
while (ssh->serverState < SERVER_DONE) {
if (DoReceiveHandshake(ssh) < WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, connectError,
"CLIENT_CHANNEL_REQUEST_SENT", ssh->error);
return WS_FATAL_ERROR;
}
}
ssh->connectState = CONNECT_SERVER_CHANNEL_REQUEST_DONE;
WLOG(WS_LOG_DEBUG, connectState,
"SERVER_CHANNEL_REQUEST_DONE");
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_connect()");
return WS_SUCCESS;
}
#endif /* NO_WOLFSSH_CLIENT */
/* See wolfssh/internal.h for the contract. */
int SendAfterDisconnect(WOLFSSH* ssh)
{
if (ssh->disconnected) {
WLOG(WS_LOG_DEBUG, "Send attempted after a disconnect");
ssh->error = WS_DISCONNECT;
return 1;
}
return 0;
}
/* Whatever a short send left in the output buffer still has to reach the
* peer, and flushing bytes that are already bundled is not the new traffic
* RFC 4253 section 11.1 forbids. Once the peer has disconnected, though,
* only our own queued disconnect still qualifies: anything else in there
* belongs to a session that is over. Call only after a NULL check of ssh. */
static int FlushQueuedOutput(WOLFSSH* ssh)
{
if (!wolfSSH_OutputPending(ssh))
return 0;
if (ssh->disconnected && !ssh->disconnectTxd)
return 0;
WLOG(WS_LOG_DEBUG, "Flushing output left queued by a short send");
return 1;
}
int wolfSSH_shutdown(WOLFSSH* ssh)
{
int ret = WS_SUCCESS;
int flushRet = WS_SUCCESS;
int flushed = 0;
WOLFSSH_CHANNEL* channel = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_shutdown()");
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
/* This is a teardown call, so anything a short send left queued goes out
* here, with or without a channel to tear down. A rejected auth's
* USERAUTH_FAILURE has no channel, and a channel close is retired off
* the channel the moment it is bundled. */
if (ret == WS_SUCCESS && FlushQueuedOutput(ssh)) {
flushRet = wolfSSH_SendPacket(ssh);
flushed = flushRet == WS_SUCCESS;
}
if (ret == WS_SUCCESS && ssh->channelList == NULL)
ret = WS_BAD_ARGUMENT;
/* The session channel is the head of the list. */
if (ret == WS_SUCCESS) {
channel = ssh->channelList;
}
/* Session already over. Drop the channel to skip the teardown sends
* and the wait for a close that will not come. RFC 4253 section 11.1. */
if (channel != NULL && ssh->disconnected) {
WLOG(WS_LOG_DEBUG, "Session already disconnected, nothing to send");
channel = NULL;
}
/* Report the dead session with or without a channel to drop: callers
* gate their retry on ssh->error, and the flush above may have just
* emptied the output buffer they would be retrying for. An unfinished
* flush owns the error instead, since that retry is still owed. */
if (ssh != NULL && ssh->disconnected && flushRet == WS_SUCCESS)
ssh->error = WS_DISCONNECT;
/* A live session has no WS_DISCONNECT to displace that stale error with,
* and the widened flush reaches sessions that are still up. The write the
* short send latched WS_WANT_WRITE for is the one that just finished, so
* it is not owed twice. */
else if (flushed && ssh->error == WS_WANT_WRITE)
ssh->error = WS_SUCCESS;
/* if channel close was not already sent then send it */
if (channel != NULL && !channel->closeTxd) {
if (ret == WS_SUCCESS) {
ret = SendChannelEof(ssh, channel->peerChannel);
}
/* continue on success and in case where queueing up send packets */
if (ret == WS_SUCCESS ||
(ret != WS_BAD_ARGUMENT && ssh->error == WS_WANT_WRITE)) {
ret = SendChannelExit(ssh, channel->peerChannel,
#if defined(WOLFSSH_TERM) || defined(WOLFSSH_SHELL)
ssh->exitStatus);
#else
0);
#endif
}
/* continue on success and in case where queueing up send packets */
if (ret == WS_SUCCESS ||
(ret != WS_BAD_ARGUMENT && ssh->error == WS_WANT_WRITE))
ret = SendChannelClose(ssh, channel->peerChannel);
}
/* if the channel was not yet removed then read to get
* response to SendChannelClose. Not while the flush left output queued:
* the peer cannot answer a close it has not finished receiving, and the
* worker has nothing to read, so a want-read from it would send the
* caller to wait on the wrong side of the socket. */
if (channel != NULL && ret == WS_SUCCESS && !wolfSSH_OutputPending(ssh)) {
ret = wolfSSH_worker(ssh, NULL);
if (ret == WS_CHAN_RXD || ret == WS_EOF) {
/* received response */
ret = WS_SUCCESS;
}
/* Report a write still owed, or the send's own error if it failed. */
if (ret == WS_SUCCESS && wolfSSH_OutputPending(ssh)) {
int sendErr = wolfSSH_get_error(ssh);
if (sendErr == WS_WANT_WRITE || sendErr == WS_WANT_READ
|| sendErr == WS_SUCCESS)
ret = WS_WANT_WRITE;
else
ret = sendErr;
}
}
if (ssh != NULL && ssh->channelList == NULL) {
WLOG(WS_LOG_DEBUG, "channel list was already removed");
ret = WS_CHANNEL_CLOSED;
}
/* An unfinished flush outranks the channel status: the caller has to
* come back for the rest of the disconnect. Not so once a teardown send
* has carried the leftovers out with it: that write is settled, and the
* teardown result stands. Nor does it outrank a teardown send that
* failed: a reset leaves the buffer intact, so the flush still reads as
* owed while the send holds the real reason. A flush that failed
* outright reports whatever else happened. */
if (flushRet != WS_SUCCESS &&
(flushRet != WS_WANT_WRITE ||
(wolfSSH_OutputPending(ssh) &&
(ret == WS_SUCCESS || ret == WS_CHANNEL_CLOSED))))
ret = flushRet;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_shutdown(), ret = %d", ret);
return ret;
}
int wolfSSH_TriggerKeyExchange(WOLFSSH* ssh)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_TriggerKeyExchange()");
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
ret = SendKexInit(ssh);
if (ret != WS_SUCCESS)
ssh->error = ret;
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_TriggerKeyExchange(), ret = %d", ret);
return ret;
}
/* gets current input buffer if any without advancing the internal index.
* returns number of bytes was able to peek at on success */
int wolfSSH_stream_peek(WOLFSSH* ssh, byte* buf, word32 bufSz)
{
WOLFSSH_BUFFER* inputBuffer;
word32 avail;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_peek()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
if (ssh->channelList == NULL) {
/* No channel left to drain, so the disconnect is all there is. */
if (ssh->disconnected) {
ssh->error = WS_DISCONNECT;
return WS_FATAL_ERROR;
}
return WS_BAD_ARGUMENT;
}
/* A rekey the peer abandoned with a disconnect never completes, since
* only NEWKEYS clears the flag. Report the dead session instead, or the
* caller turns the crank forever. */
if (ssh->isKeying && !ssh->disconnected) {
ssh->error = WS_REKEYING;
return WS_REKEYING;
}
inputBuffer = &ssh->channelList->inputBuffer;
avail = inputBuffer->length - inputBuffer->idx;
/* Report the EOF only once the buffered data is drained. */
if (avail == 0 && ssh->channelList->eofRxd) {
ssh->error = WS_EOF;
return WS_ERROR;
}
/* Report the disconnect only once the buffered data is drained, the
* same way wolfSSH_stream_read() does. Callers use this to tell a
* drained channel from one with more to come, and a dead session is
* neither. The EOF above outranks it: it names the channel. */
if (avail == 0 && ssh->disconnected) {
ssh->error = WS_DISCONNECT;
return WS_FATAL_ERROR;
}
bufSz = min(bufSz, avail);
if (buf != NULL) {
WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, bufSz);
}
return (int)bufSz;
}
static int _UpdateChannelWindow(WOLFSSH_CHANNEL* channel);
static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz);
/* Wrapper function for ease of use to get data after it has been decrypted from
* the SSH connection. This function handles low level operations in addition to
* the read, such as window adjustment and high water checking.
*
* In non blocking mode check wolfSSH_get_error(ssh) after the read: it holds
* WS_WANT_READ / WS_WANT_WRITE for a fail case, and for a success the status
* of a window adjust that could not be sent.
*
* Returns the number of bytes read on success, negative values on fail
*/
int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz)
{
int ret = WS_SUCCESS;
WOLFSSH_BUFFER* inputBuffer;
word32 headId;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_read()");
if (ssh == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
if (ssh->channelList == NULL) {
/* No channel left to drain, so the disconnect is all there is. */
if (ssh->disconnected) {
ssh->error = WS_DISCONNECT;
return WS_FATAL_ERROR;
}
return WS_BAD_ARGUMENT;
}
inputBuffer = &ssh->channelList->inputBuffer;
/* inputBuffer belongs to this channel; DoReceive() can retire it. */
headId = ssh->channelList->channel;
/* Report the EOF only once the buffered data is drained. */
if (inputBuffer->length - inputBuffer->idx == 0
&& ssh->channelList->eofRxd) {
ssh->error = WS_EOF;
return WS_ERROR;
}
/* See wolfSSH_stream_peek(): a disconnect ends a rekey that can no
* longer finish, so it outranks it here too. */
if (ssh->isKeying && !ssh->disconnected) {
ssh->error = WS_REKEYING;
return WS_FATAL_ERROR;
}
ssh->error = WS_SUCCESS;
/* Hand back whatever arrived before the disconnect, then report it once
* the buffer runs dry rather than going back to a dead transport. */
if (ssh->disconnected && inputBuffer->length - inputBuffer->idx == 0) {
ssh->error = WS_DISCONNECT;
return WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, " Stream read index of %u", inputBuffer->idx);
WLOG(WS_LOG_DEBUG, " Stream read ava data %u", inputBuffer->length);
while (inputBuffer->length - inputBuffer->idx == 0) {
WLOG(WS_LOG_DEBUG,
"Starting to receive data at current index of %u",
inputBuffer->idx);
ret = DoReceive(ssh);
/* Off the current head: DoReceive() may have retired the old. */
if (ssh->channelList == NULL
|| (ssh->channelList->eofRxd
&& ssh->channelList->inputBuffer.length
- ssh->channelList->inputBuffer.idx == 0))
ret = WS_EOF;
if (ret == WS_EOF && ssh->channelList != NULL
&& ssh->channelList->channel == headId
&& ssh->lastRxId != headId) {
/* Another channel's EOF is not this read's; the head is
* still open. Loop only while the head is unchanged, since
* inputBuffer points into it. */
continue;
}
if (ret == WS_EXTDATA &&
ssh->lastRxId != ssh->channelList->channel) {
/* Extended data for another channel. wolfSSH_extended_data_read()
* only drains the head of the list, so reporting it here would
* strand the data with its window charged. Filter it like
* WS_CHAN_RXD below; multi-channel apps read with
* wolfSSH_ChannelIdReadExt(). */
ret = WS_ERROR;
break;
}
if (ret < 0 && ret != WS_CHAN_RXD) {
break;
}
if (ssh->error == WS_CHAN_RXD) {
if (ssh->lastRxId != ssh->channelList->channel) {
ret = WS_ERROR;
break;
}
else {
ret = WS_SUCCESS;
}
}
}
}
/* update internal input buffer based on data read. DoReceive() above may
* have started a rekey, which holds the copy back -- unless a disconnect
* came with it, since then the rekey never finishes and the buffered data
* would never be handed back. */
if (ret == WS_SUCCESS && (!ssh->isKeying || ssh->disconnected)) {
int n;
n = min(bufSz, inputBuffer->length - inputBuffer->idx);
if (n <= 0)
ret = WS_BUFFER_E;
else {
WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, n);
inputBuffer->idx += n;
ret = _UpdateChannelWindow(ssh->channelList);
if (ret != WS_SUCCESS) {
ssh->error = ret;
if (ret != WS_WANT_WRITE) {
WLOG(WS_LOG_ERROR,
"wolfSSH_stream_read: window adjust send failed "
"(%d); read still succeeded", ret);
}
}
ret = n;
}
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_stream_read(), rxd = %d", ret);
return ret;
}
int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)
{
int bytesTxd = 0;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_send()");
if (ssh == NULL || buf == NULL)
return WS_BAD_ARGUMENT;
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
if (ssh->channelList == NULL)
return WS_BAD_ARGUMENT;
if (ssh->isKeying) {
ssh->error = WS_REKEYING;
return WS_FATAL_ERROR;
}
bytesTxd = SendChannelData(ssh, ssh->channelList->channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_stream_send(), txd = %d", bytesTxd);
return bytesTxd;
}
int wolfSSH_ChannelIdSend(WOLFSSH* ssh, word32 channelId,
byte* buf, word32 bufSz)
{
WOLFSSH_CHANNEL* channel;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelIdSend(), ID = %u",
channelId);
if (ssh == NULL || buf == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL) {
WLOG(WS_LOG_DEBUG, "Invalid channel");
ret = WS_INVALID_CHANID;
}
else {
if (!channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
ret = WS_CHANNEL_NOT_CONF;
}
}
}
if (ret == WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Sending data.");
ret = SendChannelData(ssh, channelId, buf, bufSz);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelIdSend(), txd = %d", ret);
return ret;
}
int wolfSSH_ChannelIdSendExt(WOLFSSH* ssh, word32 channelId,
byte* buf, word32 bufSz)
{
WOLFSSH_CHANNEL* channel;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelIdSendExt(), ID = %u",
channelId);
if (ssh == NULL || buf == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL) {
WLOG(WS_LOG_DEBUG, "Invalid channel");
ret = WS_INVALID_CHANID;
}
else {
if (!channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
ret = WS_CHANNEL_NOT_CONF;
}
}
}
if (ret == WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Sending extended data.");
ret = SendChannelExtendedData(ssh, channelId, buf, bufSz);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelIdSendExt(), txd = %d", ret);
return ret;
}
int wolfSSH_stream_send_eof(WOLFSSH* ssh)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_send_eof()");
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
/* Ahead of the channel-list test, like the other stream calls, so a
* torn-down session reports the disconnect and not a bad argument. */
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && ssh->channelList == NULL)
ret = WS_BAD_ARGUMENT;
/* Only KEX traffic may go out mid-rekey, RFC 4253 section 7.1. */
if (ret == WS_SUCCESS && ssh->isKeying) {
ssh->error = WS_REKEYING;
ret = WS_REKEYING;
}
/* Same peer-id lookup as wolfSSH_ChannelSendEof(), so the same guard. */
if (ret == WS_SUCCESS && !ssh->channelList->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
ret = WS_CHANNEL_NOT_CONF;
}
if (ret == WS_SUCCESS)
ret = SendChannelEof(ssh, ssh->channelList->peerChannel);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_stream_send_eof(), ret = %d", ret);
return ret;
}
int wolfSSH_stream_exit(WOLFSSH* ssh, int status)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_exit(), status = %d", status);
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
/* Ahead of the channel-list test, like the other stream calls, so a
* torn-down session reports the disconnect and not a bad argument. */
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && ssh->channelList == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS)
ret = SendChannelExit(ssh, ssh->channelList->peerChannel, status);
if (ret == WS_SUCCESS)
ret = SendChannelEow(ssh, ssh->channelList->peerChannel);
if (ret == WS_SUCCESS)
ret = SendChannelEof(ssh, ssh->channelList->peerChannel);
if (ret == WS_SUCCESS)
ret = SendChannelClose(ssh, ssh->channelList->peerChannel);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_stream_exit()");
return ret;
}
int wolfSSH_global_request(WOLFSSH *ssh, const unsigned char* data, word32 dataSz, int reply)
{
int ret;
#ifdef WOLFSSH_FWD
WOLFSSH_FWD_PENDING pend;
#endif
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_global_request");
if (ssh == NULL || data == NULL)
return WS_BAD_ARGUMENT;
if (reply != 0 && reply != 1)
return WS_BAD_ARGUMENT;
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
#ifdef WOLFSSH_FWD
/* A want-reply request consumes one of the peer's replies, so it takes a
* place in the same queue the forwarding requests use; otherwise its reply
* reads as the answer to an outstanding tcpip-forward. */
if (reply) {
int sent = 0;
ret = FwdReplyPrepare(ssh, &pend);
if (ret != WS_SUCCESS)
return ret;
/* A request the peer received is owed a reply whatever this call
* returns, so the slot goes by what reached the wire, not by the
* error. */
ret = SendGlobalRequest(ssh, data, dataSz, reply, &sent);
if (sent)
FwdPendingCommit(ssh, &pend);
else
FwdPendingDiscard(ssh, &pend);
return ret;
}
#endif /* WOLFSSH_FWD */
ret = SendGlobalRequest(ssh, data, dataSz, reply, NULL);
return ret;
}
int wolfSSH_extended_data_send(WOLFSSH* ssh, byte* buf, word32 bufSz)
{
int bytesTxd = 0;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_extended_data_send()");
if (ssh == NULL || buf == NULL)
return WS_BAD_ARGUMENT;
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
if (ssh->channelList == NULL)
return WS_BAD_ARGUMENT;
if (ssh->isKeying) {
ssh->error = WS_REKEYING;
return WS_REKEYING;
}
bytesTxd = SendChannelExtendedData(ssh, ssh->channelList->channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_extended_data_send(), txd = %d", bytesTxd);
return bytesTxd;
}
/* Reads STDERR from the channel at the head of the channel list, like
* wolfSSH_stream_read(). See wolfssh/ssh.h for the contract. */
int wolfSSH_extended_data_read(WOLFSSH* ssh, byte* out, word32 outSz)
{
int bytesRxd;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_extended_data_read()");
if (ssh == NULL || out == NULL || ssh->channelList == NULL)
return WS_BAD_ARGUMENT;
bytesRxd = _ChannelReadExt(ssh->channelList, out, outSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_extended_data_read(), rxd = %d",
bytesRxd);
return bytesRxd;
}
int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz)
{
byte scratch[128];
WOLFSSH_UNUSED(buf);
WOLFSSH_UNUSED(bufSz);
if (ssh == NULL)
return WS_BAD_ARGUMENT;
if (SendAfterDisconnect(ssh))
return WS_FATAL_ERROR;
WMEMSET(scratch, 0, sizeof(scratch));
return SendIgnore(ssh, scratch, sizeof(scratch));
}
int wolfSSH_SendDisconnect(WOLFSSH *ssh, word32 reason)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendDisconnect");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
/* One disconnect ends the session; a second is more traffic on a
* connection that is already over. A short send leaves the first one
* queued, though, so that retry goes through. */
if (SendAfterDisconnect(ssh)) {
if (FlushQueuedOutput(ssh))
return wolfSSH_SendPacket(ssh);
return WS_FATAL_ERROR;
}
return SendDisconnect(ssh, reason);
}
void wolfSSH_SetUserAuth(WOLFSSH_CTX* ctx, WS_CallbackUserAuth cb)
{
if (ctx != NULL) {
ctx->userAuthCb = cb;
}
}
void wolfSSH_SetUserAuthTypes(WOLFSSH_CTX* ctx, WS_CallbackUserAuthTypes cb)
{
if (ctx != NULL) {
ctx->userAuthTypesCb = cb;
}
}
void wolfSSH_SetUserAuthCtx(WOLFSSH* ssh, void* userAuthCtx)
{
if (ssh != NULL) {
ssh->userAuthCtx = userAuthCtx;
}
}
void* wolfSSH_GetUserAuthCtx(WOLFSSH* ssh)
{
if (ssh != NULL) {
return ssh->userAuthCtx;
}
return NULL;
}
void wolfSSH_SetUserAuthResult(WOLFSSH_CTX* ctx,
WS_CallbackUserAuthResult cb)
{
if (ctx != NULL) {
ctx->userAuthResultCb = cb;
}
}
void wolfSSH_SetUserAuthResultCtx(WOLFSSH* ssh, void* userAuthResultCtx)
{
if (ssh != NULL) {
ssh->userAuthResultCtx = userAuthResultCtx;
}
}
void* wolfSSH_GetUserAuthResultCtx(WOLFSSH* ssh)
{
if (ssh != NULL) {
return ssh->userAuthResultCtx;
}
return NULL;
}
void wolfSSH_CTX_SetPublicKeyCheck(WOLFSSH_CTX* ctx,
WS_CallbackPublicKeyCheck cb)
{
if (ctx != NULL) {
ctx->publicKeyCheckCb = cb;
}
}
void wolfSSH_SetPublicKeyCheckCtx(WOLFSSH* ssh, void* publicKeyCheckCtx)
{
if (ssh != NULL) {
ssh->publicKeyCheckCtx = publicKeyCheckCtx;
}
}
void* wolfSSH_GetPublicKeyCheckCtx(WOLFSSH* ssh)
{
if (ssh != NULL) {
return ssh->publicKeyCheckCtx;
}
return NULL;
}
#if defined(WOLFSSH_TERM) && !defined(NO_FILESYSTEM)
/* Used to resize terminal window with shell connections
* returns WS_SUCCESS on success */
int wolfSSH_ChangeTerminalSize(WOLFSSH* ssh, word32 columns, word32 rows,
word32 widthPixels, word32 heightPixels)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChangeWindowDimension()");
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
ret = SendChannelTerminalResize(ssh, columns, rows, widthPixels,
heightPixels);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChangeWindowDimension(), ret = %d",
ret);
return ret;
}
void wolfSSH_SetTerminalResizeCb(WOLFSSH* ssh, WS_CallbackTerminalSize cb)
{
ssh->termResizeCb = cb;
}
void wolfSSH_SetTerminalResizeCtx(WOLFSSH* ssh, void* usrCtx)
{
ssh->termCtx = usrCtx;
}
#endif
#if defined(WOLFSSH_TERM) || defined(WOLFSSH_SHELL)
/* returns the exit status captured from the connection if any */
int wolfSSH_GetExitStatus(WOLFSSH* ssh)
{
if (ssh == NULL) {
WLOG(WS_LOG_DEBUG, "wolfSSH_GetExitStatus WOLFSSH struct was NULL");
return WS_BAD_ARGUMENT;
}
return ssh->exitStatus;
}
/* Sets the exit status to send on shutdown
* returns WS_SUCCESS on success */
int wolfSSH_SetExitStatus(WOLFSSH* ssh, word32 exitStatus)
{
if (ssh == NULL) {
WLOG(WS_LOG_DEBUG, "wolfSSH_SetExitStatus WOLFSSH struct was NULL");
return WS_BAD_ARGUMENT;
}
WLOG(WS_LOG_DEBUG, "wolfSSH_SetExitStatus sending exit status %u",
exitStatus);
ssh->exitStatus = exitStatus;
return WS_SUCCESS;
}
#endif
/* Used to set the channel request type sent in wolfSSH connect. The default
* type set is shell if this function is not called.
*
* type channel type i.e. WOLFSSH_SESSION_SUBSYSTEM
* name name or command in the case of subsystem and exec channel types
* nameSz size of name buffer
*
* Exec and subsystem carry a name string the peer requires, so one must be
* available. Passing none keeps the name an earlier call stored; with
* nothing stored the call is refused rather than sending a request the peer
* reads as malformed. Shell and terminal take no name and drop any stored
* one. A refused call changes nothing, the selected type included.
*
* returns WS_SUCCESS on success
* returns WS_BAD_ARGUMENT for a NULL ssh or an unknown type, for exec on
* the server side, for a name at or above WOLFSSH_MAX_CHN_NAMESZ, for a
* nameSz with no name behind it, and for exec or subsystem with no name
* given and none stored
* returns WS_MEMORY_E if the name cannot be allocated
*/
int wolfSSH_SetChannelType(WOLFSSH* ssh, byte type, byte* name, word32 nameSz)
{
if (ssh == NULL) {
return WS_BAD_ARGUMENT;
}
switch (type) {
case WOLFSSH_SESSION_SHELL:
/* shell has no name; drop any name left by a prior subsystem/exec */
if (ssh->channelName != NULL) {
WFREE(ssh->channelName, ssh->ctx->heap, DYNTYPE_STRING);
ssh->channelName = NULL;
}
ssh->channelNameSz = 0;
ssh->connectChannelId = type;
break;
case WOLFSSH_SESSION_EXEC:
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {
WLOG(WS_LOG_DEBUG, "Server side exec unsupported");
return WS_BAD_ARGUMENT;
}
FALL_THROUGH;
case WOLFSSH_SESSION_SUBSYSTEM: {
byte* newName;
if (name == NULL && nameSz > 0) {
WLOG(WS_LOG_DEBUG, "Channel name size without a name");
return WS_BAD_ARGUMENT;
}
if (name != NULL && nameSz >= WOLFSSH_MAX_CHN_NAMESZ) {
/* Report it. Dropping the name sends a request with no name
* string, which the peer rejects as malformed. */
WLOG(WS_LOG_DEBUG, "Channel name too large");
return WS_BAD_ARGUMENT;
}
if (name != NULL && nameSz > 0) {
/* only (re)allocate when the name changed; SFTP/SCP retry
* loops re-set the same name on every poll */
if (ssh->channelName == NULL || ssh->channelNameSz != nameSz ||
WMEMCMP(ssh->channelName, name, nameSz) != 0) {
newName = (byte*)WMALLOC(nameSz + 1, ssh->ctx->heap,
DYNTYPE_STRING);
if (newName == NULL) {
return WS_MEMORY_E;
}
WMEMCPY(newName, name, nameSz);
newName[nameSz] = 0;
if (ssh->channelName != NULL) {
WFREE(ssh->channelName, ssh->ctx->heap, DYNTYPE_STRING);
}
ssh->channelName = newName;
ssh->channelNameSz = nameSz;
}
}
else if (ssh->channelName == NULL) {
/* No name now and none from an earlier call. Same reason as
* the oversize case: exec and subsystem both carry a
* required name string, and SendChannelRequest() leaves the
* field out entirely when it has nothing to put there. */
WLOG(WS_LOG_DEBUG, "No channel name to send");
return WS_BAD_ARGUMENT;
}
else {
/* keep the name an earlier call stored; SFTP/SCP retry
* loops re-enter with nothing to say */
WLOG(WS_LOG_DEBUG, "Keeping the stored channel name");
}
ssh->connectChannelId = type;
break;
}
#ifdef WOLFSSH_TERM
case WOLFSSH_SESSION_TERMINAL:
/* send a pseudo-terminal request and shell channel */
ssh->sendTerminalRequest = 1;
if (ssh->channelName != NULL) {
WFREE(ssh->channelName, ssh->ctx->heap, DYNTYPE_STRING);
ssh->channelName = NULL;
}
ssh->channelNameSz = 0;
ssh->connectChannelId = WOLFSSH_SESSION_SHELL;
break;
#endif
default:
WLOG(WS_LOG_DEBUG, "Unknown channel type");
return WS_BAD_ARGUMENT;
}
return WS_SUCCESS;
}
int wolfSSH_SetUsernameRaw(WOLFSSH* ssh,
const byte* username, word32 usernameSz)
{
char* newUsername = NULL;
int ret = WS_SUCCESS;
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
if (username == NULL || usernameSz == 0)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS) {
newUsername = (char*)WMALLOC(usernameSz + 1,
ssh->ctx->heap, DYNTYPE_STRING);
if (newUsername == NULL)
ret = WS_MEMORY_E;
else {
WMEMCPY(newUsername, username, usernameSz);
newUsername[usernameSz] = 0;
if (ssh->userName != NULL)
WFREE(ssh->userName, ssh->ctx->heap, DYNTYPE_STRING);
ssh->userName = newUsername;
ssh->userNameSz = usernameSz;
}
}
return ret;
}
int wolfSSH_SetUsername(WOLFSSH* ssh, const char* username)
{
int ret = WS_SUCCESS;
if (ssh == NULL || username == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS) {
ret = wolfSSH_SetUsernameRaw(ssh,
(const byte*)username, (word32)WSTRLEN(username));
}
return ret;
}
char* wolfSSH_GetUsername(WOLFSSH* ssh)
{
char* name = NULL;
if (ssh != NULL)
name = ssh->userName;
return name;
}
#include <wolfssl/wolfcrypt/rsa.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/coding.h>
#ifndef WOLFSSH_NO_MLDSA
#include <wolfssl/wolfcrypt/dilithium.h>
#endif
#ifdef WOLFSSH_OSSH_CERTS
#include <wolfssh/ossh.h>
#endif
union wolfSSH_key {
#ifndef WOLFSSH_NO_RSA
RsaKey rsa;
#endif
#ifndef WOLFSSH_NO_ECDSA
ecc_key ecc;
#endif
};
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* currently only used in wolfSSH_ReadKey_file() */
static const char* PrivBeginOpenSSH = "-----BEGIN OPENSSH PRIVATE KEY-----";
static const char* PrivBeginPrefix = "-----BEGIN ";
/* static const char* PrivEndPrefix = "-----END "; */
static const char* PrivSuffix = " PRIVATE KEY-----";
#endif
static int DoSshPubKey(const byte* in, word32 inSz, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz,
void* heap)
{
byte* newKey = NULL;
char* c;
char* last;
char* type = NULL;
char* key = NULL;
int ret = WS_SUCCESS;
word32 newKeySz, typeSz = 0;
WOLFSSH_UNUSED(inSz);
WOLFSSH_UNUSED(heap);
/*
SSH format is:
type AAAABASE64ENCODEDKEYDATA comment
allocate a copy to tokenize, add a null terminator.
*/
c = (char*)WMALLOC(inSz + 1, heap, DYNTYPE_STRING);
if (c != NULL) {
WMEMCPY(c, in, inSz);
c[inSz] = 0;
type = WSTRTOK(c, " \n", &last);
key = WSTRTOK(NULL, " \n", &last);
}
else {
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS) {
if (type == NULL || key == NULL) {
ret = WS_PARSE_E;
}
}
if (ret == WS_SUCCESS) {
typeSz = (word32)WSTRLEN(type);
/* set size based on sanity check in wolfSSL base64 decode
* function */
newKeySz = ((word32)WSTRLEN(key) * 3 + 3) / 4;
if (*out == NULL) {
newKey = (byte*)WMALLOC(newKeySz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
}
else {
if (*outSz < newKeySz) {
WLOG(WS_LOG_DEBUG, "PEM private key output size too small");
ret = WS_BUFFER_E;
}
else {
newKey = *out;
}
}
}
if (ret == WS_SUCCESS) {
ret = Base64_Decode((byte*)key, (word32)WSTRLEN(key),
newKey, &newKeySz);
if (ret == 0) {
*out = newKey;
*outSz = newKeySz;
*outType = (const byte *)IdToName(NameToId(type, typeSz));
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
else {
WLOG(WS_LOG_DEBUG, "Base64 decode of public key failed.");
if (*out == NULL) {
WFREE(newKey, heap, DYNTYPE_PRIVKEY);
}
ret = WS_PARSE_E;
}
}
WFREE(c, heap, DYNTYPE_STRING);
return ret;
}
static int DoAsn1Key(const byte* in, word32 inSz, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz,
int isPrivate, void* heap)
{
int ret = WS_SUCCESS;
byte* newKey = NULL;
WS_KeySignature* key = NULL;
WOLFSSH_UNUSED(heap);
ret = IdentifyAsn1Key(in, inSz, isPrivate, heap, &key);
if (ret <= 0) {
WLOG(WS_LOG_DEBUG, "Unable to identify ASN.1 key");
}
if (ret > 0 && !isPrivate) {
*outType = (const byte*)IdToName(ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
#ifndef WOLFSSH_NO_MLDSA
if (
#ifndef WOLFSSH_NO_MLDSA44
ret == ID_MLDSA44 ||
#endif
#ifndef WOLFSSH_NO_MLDSA65
ret == ID_MLDSA65 ||
#endif
#ifndef WOLFSSH_NO_MLDSA87
ret == ID_MLDSA87 ||
#endif
0) {
byte* rawPub = NULL;
word32 rawPubSz = WOLFSSH_MLDSA_MAX_PUB_KEY_SZ;
const char* name = (const char*)*outType;
word32 nameLen = *outTypeSz;
word32 localIdx = 0;
rawPub = (byte*)WMALLOC(rawPubSz, heap, DYNTYPE_PUBKEY);
if (rawPub == NULL) {
ret = WS_MEMORY_E;
}
else {
int wcRet = wc_MlDsaKey_ExportPubRaw(&key->ks.mldsa.key,
rawPub, &rawPubSz);
if (wcRet != 0) {
ret = WS_CRYPTO_FAILED;
}
else {
*outSz = LENGTH_SZ + nameLen + LENGTH_SZ + rawPubSz;
newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
else {
c32toa(nameLen, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], name, nameLen);
localIdx += nameLen;
c32toa(rawPubSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], rawPub, rawPubSz);
*out = newKey;
ret = WS_SUCCESS;
}
}
WFREE(rawPub, heap, DYNTYPE_PUBKEY);
}
}
/* Each arm's else sits inside that arm's guard, so a disabled key
* type drops its if and its else together and the chain stays
* well formed. */
else
#endif /* WOLFSSH_NO_MLDSA */
#ifndef WOLFSSH_NO_ECDSA
if (ret == ID_ECDSA_SHA2_NISTP256 ||
ret == ID_ECDSA_SHA2_NISTP384 ||
ret == ID_ECDSA_SHA2_NISTP521) {
byte* q = NULL;
/* uncompressed x963 point: leading tag byte plus X and Y */
word32 qSz = 1 + 2 * MAX_ECC_BYTES;
const char* curveName;
word32 curveNameSz;
word32 localIdx = 0;
if (ret == ID_ECDSA_SHA2_NISTP384) {
curveName = IdToName(ID_CURVE_NISTP384);
}
else if (ret == ID_ECDSA_SHA2_NISTP521) {
curveName = IdToName(ID_CURVE_NISTP521);
}
else {
curveName = IdToName(ID_CURVE_NISTP256);
}
curveNameSz = (word32)WSTRLEN(curveName);
q = (byte*)WMALLOC(qSz, heap, DYNTYPE_PUBKEY);
if (q == NULL) {
ret = WS_MEMORY_E;
}
else {
int wcRet;
PRIVATE_KEY_UNLOCK();
wcRet = wc_ecc_export_x963(&key->ks.ecc.key, q, &qSz);
PRIVATE_KEY_LOCK();
if (wcRet != 0) {
ret = WS_CRYPTO_FAILED;
}
else {
*outSz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + curveNameSz +
LENGTH_SZ + qSz;
newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
else {
c32toa(*outTypeSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], *outType, *outTypeSz);
localIdx += *outTypeSz;
c32toa(curveNameSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], curveName, curveNameSz);
localIdx += curveNameSz;
c32toa(qSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], q, qSz);
*out = newKey;
ret = WS_SUCCESS;
}
}
WFREE(q, heap, DYNTYPE_PUBKEY);
}
}
else
#endif /* WOLFSSH_NO_ECDSA */
#ifndef WOLFSSH_NO_ED25519
if (ret == ID_ED25519) {
byte q[ED25519_PUB_KEY_SIZE];
word32 qSz = (word32)sizeof(q);
word32 localIdx = 0;
int wcRet;
wcRet = wc_ed25519_export_public(&key->ks.ed25519.key, q, &qSz);
if (wcRet != 0) {
ret = WS_CRYPTO_FAILED;
}
else {
*outSz = LENGTH_SZ + *outTypeSz + LENGTH_SZ + qSz;
newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
else {
c32toa(*outTypeSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], *outType, *outTypeSz);
localIdx += *outTypeSz;
c32toa(qSz, &newKey[localIdx]);
localIdx += LENGTH_SZ;
WMEMCPY(&newKey[localIdx], q, qSz);
*out = newKey;
ret = WS_SUCCESS;
}
}
}
else
#endif /* WOLFSSH_NO_ED25519 */
#ifndef WOLFSSH_NO_RSA
if (ret == ID_SSH_RSA) {
long e;
byte n[RSA_MAX_SIZE]; /* TODO: Handle small stack */
word32 nSz = (word32)sizeof(n), eSz = (word32)sizeof(e);
const char* keyFormat = "ssh-rsa";
word32 idx = 0;
int nMsb = 0;
int wcRet;
wcRet = wc_RsaFlattenPublicKey(&key->ks.rsa.key, (byte*)&e, &eSz,
n, &nSz);
if (wcRet != 0) {
ret = WS_CRYPTO_FAILED;
}
else {
ret = WS_SUCCESS;
}
if (ret == WS_SUCCESS) {
if (n[0] & 0x80) {
/* if MSB is set need leading zero */
nMsb = 1;
}
*outSz = LENGTH_SZ + (word32)WSTRLEN(keyFormat) +
LENGTH_SZ + eSz +
LENGTH_SZ + nSz + nMsb;
newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
}
if (ret == WS_SUCCESS) {
/* encode the key format string */
c32toa((word32)WSTRLEN(keyFormat), &newKey[idx]);
idx += LENGTH_SZ;
WMEMCPY(&newKey[idx], keyFormat, (word32)WSTRLEN(keyFormat));
idx += (word32)WSTRLEN(keyFormat);
/* encode public exponent (e) */
c32toa(eSz, &newKey[idx]);
idx += LENGTH_SZ;
WMEMCPY(&newKey[idx], &e, eSz);
idx += eSz;
/* encode public modulus (n) */
c32toa(nSz + nMsb, &newKey[idx]);
idx += LENGTH_SZ;
if (nMsb) {
newKey[idx++] = 0;
}
WMEMCPY(&newKey[idx], n, nSz);
*out = newKey;
}
}
else
#endif /* WOLFSSH_NO_RSA */
{
WLOG(WS_LOG_DEBUG, "DoAsn1Key unsupported public key type");
ret = WS_UNIMPLEMENTED_E;
}
}
else if (ret > 0 && isPrivate) {
if (*out == NULL) {
newKey = (byte*)WMALLOC(inSz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
ret = WS_MEMORY_E;
}
}
else if (*outSz < inSz) {
WLOG(WS_LOG_DEBUG, "DER private key output size too small");
ret = WS_BUFFER_E;
}
else {
newKey = *out;
}
if (ret > 0) {
*out = newKey;
*outSz = inSz;
WMEMCPY(newKey, in, inSz);
*outType = (const byte*)IdToName(ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
}
}
wolfSSH_KEY_clean(key);
WFREE(key, heap, isPrivate ? DYNTYPE_PRIVKEY : DYNTYPE_PUBKEY);
if (*out == NULL) {
WFREE(newKey, heap, DYNTYPE_PRIVKEY);
}
if (ret > 0) {
ret = WS_SUCCESS;
}
return ret;
}
static int DoPemKey(const byte* in, word32 inSz, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz,
int isPrivate, void* heap)
{
int ret = WS_SUCCESS;
byte* newKey = NULL;
word32 newKeySz = inSz; /* binary will be smaller than PEM */
WOLFSSH_UNUSED(heap);
if (*out == NULL) {
newKey = (byte*)WMALLOC(newKeySz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
return WS_MEMORY_E;
}
}
else {
if (*outSz < inSz) {
WLOG(WS_LOG_DEBUG, "PEM private key output size too small");
return WS_BUFFER_E;
}
newKey = *out;
newKeySz = *outSz;
}
/* If it is PEM, convert to ASN1 then process. */
if (isPrivate) {
ret = wc_KeyPemToDer(in, inSz, newKey, newKeySz, NULL);
}
else {
#ifdef WOLFSSH_TPM
ret = wc_PubKeyPemToDer(in, inSz, newKey, newKeySz);
#else
ret = NOT_COMPILED_IN;
#endif
}
if (ret > 0) {
newKeySz = (word32)ret;
ret = WS_SUCCESS;
}
else {
WLOG(WS_LOG_DEBUG, "Base64 decode of public key failed.");
ret = WS_PARSE_E;
}
if (ret == WS_SUCCESS) {
ret = IdentifyAsn1Key(newKey, newKeySz, isPrivate, heap, NULL);
}
if (ret > 0) {
*out = newKey;
*outSz = newKeySz;
*outType = (const byte*)IdToName(ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
else {
WLOG(WS_LOG_DEBUG, "Unable to identify PEM key");
WS_FORCEZERO(newKey, newKeySz);
if (*out == NULL) {
WFREE(newKey, heap, DYNTYPE_PRIVKEY);
}
}
return ret;
}
static int DoOpenSshKey(const byte* in, word32 inSz, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz,
void* heap)
{
int ret = WS_SUCCESS;
byte* newKey = NULL;
word32 newKeySz = inSz; /* binary will be smaller than PEM */
if (*out == NULL) {
newKey = (byte*)WMALLOC(newKeySz, heap, DYNTYPE_PRIVKEY);
if (newKey == NULL) {
return WS_MEMORY_E;
}
}
else {
if (*outSz < inSz) {
WLOG(WS_LOG_DEBUG, "PEM private key output size too small");
return WS_BUFFER_E;
}
newKey = *out;
newKeySz = *outSz;
}
/* locates the begin/end markers and base64-decodes the block between
* them; shared with wolfSSH_ProcessBuffer()'s OPENSSH format path */
ret = WS_StripOpenSshPem(in, inSz, newKey, &newKeySz);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "OpenSSH private key marker/decode failed.");
}
if (ret == WS_SUCCESS) {
ret = IdentifyOpenSshKey(newKey, newKeySz, heap);
if (ret <= 0) {
WLOG(WS_LOG_DEBUG, "Unable to identify key");
}
}
if (ret > 0) {
*out = newKey;
*outSz = newKeySz;
*outType = (const byte*)IdToName(ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
else {
WS_FORCEZERO(newKey, newKeySz);
if (*out == NULL) {
WFREE(newKey, heap, DYNTYPE_PRIVKEY);
}
}
return ret;
}
/* Reads a key from the buffer in to out. If the out buffer doesn't exist
it is created. The type of key is stored in outType. It'll be a pointer
to a constant string. Format indicates the format of the key, currently
either SSH format (a public key) or ASN.1 in DER or PEM format (a
private key). */
int wolfSSH_ReadKey_buffer_ex(const byte* in, word32 inSz, int format,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
int isPrivate, void* heap)
{
int ret = WS_SUCCESS;
if (in == NULL || inSz == 0 || out == NULL || outSz == NULL ||
outType == NULL || outTypeSz == NULL)
return WS_BAD_ARGUMENT;
if (format == WOLFSSH_FORMAT_SSH) {
ret = DoSshPubKey(in, inSz, out, outSz, outType, outTypeSz, heap);
}
else if (format == WOLFSSH_FORMAT_ASN1) {
ret = DoAsn1Key(in, inSz, out, outSz, outType, outTypeSz,
isPrivate, heap);
}
else if (format == WOLFSSH_FORMAT_PEM) {
ret = DoPemKey(in, inSz, out, outSz, outType, outTypeSz,
isPrivate, heap);
}
else if (format == WOLFSSH_FORMAT_OPENSSH) {
ret = DoOpenSshKey(in, inSz, out, outSz, outType, outTypeSz, heap);
}
else {
WLOG(WS_LOG_DEBUG, "Invalid key format");
ret = WS_BAD_ARGUMENT;
}
return ret;
}
int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
void* heap)
{
return wolfSSH_ReadKey_buffer_ex(in, inSz, format, out, outSz,
outType, outTypeSz, 1, heap);
}
int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
void* heap)
{
return wolfSSH_ReadKey_buffer_ex(in, inSz, format, out, outSz,
outType, outTypeSz, 0, heap);
}
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
#ifdef WOLFSSH_CERTS
static const char* CertBeginPrefix = "-----BEGIN CERTIFICATE-----";
#endif
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
static const char* TrustedCertBeginPrefix =
"-----BEGIN TRUSTED CERTIFICATE-----";
#endif
/* Longest algorithm name is ecdsa-sha2-nistp521-cert-v01@openssh.com. */
#define WOLFSSH_MAX_CERT_ALGO_NAME_SZ 48
/* Identifies a certificate from its content, without decoding or allocating.
An x509v3-* line holds a wire chain, not a certificate, so it is declined.
The trusted form is PEM too; the decoders decide who takes it. */
static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
byte* certId)
{
#ifdef WOLFSSH_OSSH_CERTS
word32 tokenSz = 0;
byte id;
#endif
int ret = WS_BAD_FILETYPE_E;
#ifdef WOLFSSH_CERTS
if (in[0] == 0x30) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_ASN1;
}
/* Searched for, not anchored: openssl writes a dump ahead of the header. */
else if (WSTRNSTR((const char*)in, CertBeginPrefix, inSz) != NULL) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_PEM;
}
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
else if (WSTRNSTR((const char*)in, TrustedCertBeginPrefix, inSz) != NULL) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_PEM;
}
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
if (ret == WS_BAD_FILETYPE_E) {
while (tokenSz < inSz && tokenSz < WOLFSSH_MAX_CERT_ALGO_NAME_SZ
&& in[tokenSz] != ' ' && in[tokenSz] != '\t'
&& in[tokenSz] != '\r' && in[tokenSz] != '\n') {
tokenSz++;
}
if (tokenSz > 0 && tokenSz < WOLFSSH_MAX_CERT_ALGO_NAME_SZ) {
id = NameToId((const char*)in, tokenSz);
switch (id) {
case ID_OSSH_CERT_RSA:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP256:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP384:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP521:
case ID_OSSH_CERT_ED25519:
*flavor = WOLFSSH_CERT_FLAVOR_OSSH;
*certId = id;
ret = WOLFSSH_FORMAT_SSH;
break;
default:
break;
}
}
}
#else
WOLFSSH_UNUSED(certId);
#endif /* WOLFSSH_OSSH_CERTS */
return ret;
}
#ifdef WOLFSSH_CERTS
/* Decodes a PEM certificate to DER and identifies it. */
static int DoPemCert(const byte* in, word32 inSz, byte** out, word32* outSz,
const byte** outType, word32* outTypeSz, void* heap)
{
byte* der;
word32 derSz = 0;
int ret;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (IsTrustedCertPem(in, inSz)) {
/* The trust data behind the certificate means nothing to a peer. */
WLOG(WS_LOG_DEBUG, "Trusted certificate form is for root CAs");
return WS_BAD_FILETYPE_E;
}
#endif
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (der == NULL) {
return WS_MEMORY_E;
}
ret = wc_CertPemToDer(in, (int)inSz, der, (int)inSz, CERT_TYPE);
if (ret <= 0) {
WLOG(WS_LOG_DEBUG, "PEM certificate body would not decode.");
WFREE(der, heap, DYNTYPE_CERT);
return WS_PARSE_E;
}
derSz = (word32)ret;
ret = IdentifyCert(der, derSz, heap);
if (ret < 0) {
WFREE(der, heap, DYNTYPE_CERT);
}
else {
*out = der;
*outSz = derSz;
*outType = (const byte*)IdToName((byte)ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
return ret;
}
/* Identifies a DER certificate before copying it out. */
static int DoDerCert(const byte* in, word32 inSz, byte** out, word32* outSz,
const byte** outType, word32* outTypeSz, void* heap)
{
byte* der;
int ret;
ret = IdentifyCert(in, inSz, heap);
if (ret >= 0) {
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (der == NULL) {
ret = WS_MEMORY_E;
}
else {
WMEMCPY(der, in, inSz);
*out = der;
*outSz = inSz;
*outType = (const byte*)IdToName((byte)ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
}
return ret;
}
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
/* Decodes an OpenSSH certificate line and checks that it parses. */
static int DoOsshCert(const byte* in, word32 inSz, byte certId, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz, void* heap)
{
WS_OsshCert cert;
byte* blob;
int ret;
blob = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (blob == NULL) {
return WS_MEMORY_E;
}
/* DoSshPubKey decodes into a non-NULL *out rather than allocating, so it
fills blob and leaves it ours to free. Were it to allocate instead,
blob would leak and the free below would take the wrong type. */
*out = blob;
*outSz = inSz;
ret = DoSshPubKey(in, inSz, out, outSz, outType, outTypeSz, heap);
if (ret == WS_SUCCESS) {
/* The blob has no envelope, so only a parse tells it from a key. */
ret = OsshCertParse(&cert, certId, *out, *outSz);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "OpenSSH certificate is malformed.");
}
}
if (ret != WS_SUCCESS) {
WFREE(blob, heap, DYNTYPE_CERT);
*out = NULL;
*outSz = 0;
}
return ret;
}
#endif /* WOLFSSH_OSSH_CERTS */
/* Reads a certificate from the buffer in to out, detecting its form from the
content. See ssh.h for what the caller owns. */
int wolfSSH_ReadCert_buffer(const byte* in, word32 inSz,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap)
{
byte certId = ID_UNKNOWN;
int format;
int ret;
if (in == NULL || inSz == 0 || out == NULL || outSz == NULL ||
outType == NULL || outTypeSz == NULL || flavor == NULL) {
return WS_BAD_ARGUMENT;
}
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
format = SniffCertForm(in, inSz, flavor, &certId);
if (format < 0) {
WLOG(WS_LOG_DEBUG, "Unable to identify the certificate");
return format;
}
ret = WS_BAD_FILETYPE_E;
switch (format) {
#ifdef WOLFSSH_CERTS
case WOLFSSH_FORMAT_PEM:
ret = DoPemCert(in, inSz, out, outSz, outType, outTypeSz, heap);
break;
case WOLFSSH_FORMAT_ASN1:
ret = DoDerCert(in, inSz, out, outSz, outType, outTypeSz, heap);
break;
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
case WOLFSSH_FORMAT_SSH:
ret = DoOsshCert(in, inSz, certId, out, outSz, outType, outTypeSz,
heap);
break;
#endif /* WOLFSSH_OSSH_CERTS */
default:
break;
}
/* The sniff names the form and a decoder may fill an out parameter before
failing, so a failure clears them all rather than leave a partial
answer. Decoders free whatever they allocated. */
if (ret != WS_SUCCESS) {
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
}
return ret;
}
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* Reads the file name into a buffer allocated with an extra byte holding a
nul terminator. The caller frees out with DYNTYPE_FILE. */
static int ReadFileIntoBuffer(const char* name, byte** out, word32* outSz,
void* heap)
{
WFILE* file;
#ifdef MICROCHIP_MPLAB_HARMONY
WFILE f = WBADFILE;
#endif
byte* in;
word32 inSz;
int ret;
#ifdef MICROCHIP_MPLAB_HARMONY
file = &f;
ret = WFOPEN(NULL, &file, name, WOLFSSH_O_RDONLY);
if (ret != 0 || *file == WBADFILE) return WS_BAD_FILE_E;
#else
ret = WFOPEN(NULL, &file, name, "rb");
if (ret != 0 || file == WBADFILE) return WS_BAD_FILE_E;
#endif
if (!WFSEEK_SUCCESS(WFSEEK(NULL, file, 0, WSEEK_END))) {
WFCLOSE(NULL, file);
return WS_BAD_FILE_E;
}
inSz = (word32)WFTELL(NULL, file);
WREWIND(NULL, file);
if (inSz > WOLFSSH_MAX_FILE_SIZE || inSz == 0) {
WFCLOSE(NULL, file);
return WS_BAD_FILE_E;
}
in = (byte*)WMALLOC(inSz + 1, heap, DYNTYPE_FILE);
if (in == NULL) {
WFCLOSE(NULL, file);
return WS_MEMORY_E;
}
ret = (int)WFREAD(NULL, in, 1, inSz, file);
WFCLOSE(NULL, file);
if (ret <= 0 || (word32)ret != inSz) {
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
return WS_BAD_FILE_E;
}
in[inSz] = 0;
*out = in;
*outSz = inSz;
return WS_SUCCESS;
}
/* Reads a key from the file name into a buffer. An SSH algorithm name marks
an SSH format public key and "-----BEGIN " a PEM one, otherwise it is an
ASN.1 private key. The buffer goes to wolfSSH_ReadKey_buffer_ex(). */
int wolfSSH_ReadKey_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* isPrivate, void* heap)
{
byte* in;
word32 inSz;
int format;
int ret;
if (name == NULL)
return WS_BAD_FILE_E;
if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL ||
isPrivate == NULL)
return WS_BAD_ARGUMENT;
ret = ReadFileIntoBuffer(name, &in, &inSz, heap);
if (ret == WS_SUCCESS) {
if (WSTRNSTR((const char*)in, "ssh-rsa", inSz) == (const char*)in
|| WSTRNSTR((const char*)in,
"ecdsa-sha2-nistp", inSz) == (const char*)in
|| WSTRNSTR((const char*)in,
"ssh-ed25519", inSz) == (const char*)in
|| WSTRNSTR((const char*)in,
"ssh-mldsa-", inSz) == (const char*)in
|| WSTRNSTR((const char*)in,
"x509v3-ssh-mldsa-", inSz) == (const char*)in) {
*isPrivate = 0;
format = WOLFSSH_FORMAT_SSH;
}
else if (WSTRNSTR((const char*)in, PrivBeginOpenSSH, inSz) != NULL) {
*isPrivate = 1;
format = WOLFSSH_FORMAT_OPENSSH;
}
else if ((WSTRNSTR((const char*)in, PrivBeginPrefix, inSz)
== (const char*)in)
&& (WSTRNSTR((const char*)in, PrivSuffix, inSz)
!= NULL)) {
*isPrivate = 1;
format = WOLFSSH_FORMAT_PEM;
}
else {
*isPrivate = 1;
format = WOLFSSH_FORMAT_ASN1;
}
ret = wolfSSH_ReadKey_buffer_ex(in, inSz, format,
out, outSz, outType, outTypeSz, *isPrivate, heap);
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
}
return ret;
}
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
/* Reads a certificate file and passes it to wolfSSH_ReadCert_buffer(). */
int wolfSSH_ReadCert_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap)
{
byte* in;
word32 inSz;
int ret;
if (name == NULL)
return WS_BAD_FILE_E;
if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL ||
flavor == NULL)
return WS_BAD_ARGUMENT;
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
ret = ReadFileIntoBuffer(name, &in, &inSz, heap);
if (ret == WS_SUCCESS) {
ret = wolfSSH_ReadCert_buffer(in, inSz,
out, outSz, outType, outTypeSz, flavor, heap);
/* A certificate is public, but the file may hold a private key
beside it, so scrub it like the key reader does. */
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
}
return ret;
}
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
#endif
int wolfSSH_CTX_SetAlgoListKex(WOLFSSH_CTX* ctx, const char* list)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx) {
ret = CheckAlgoList(list, TYPE_KEX);
if (ret == WS_SUCCESS) {
ctx->algoListKex = list;
}
}
return ret;
}
const char* wolfSSH_CTX_GetAlgoListKex(WOLFSSH_CTX* ctx)
{
const char* list = NULL;
if (ctx) {
list = ctx->algoListKex;
}
return list;
}
int wolfSSH_SetAlgoListKex(WOLFSSH* ssh, const char* list)
{
int ret = WS_SSH_NULL_E;
if (ssh) {
ret = CheckAlgoList(list, TYPE_KEX);
if (ret == WS_SUCCESS) {
ssh->algoListKex = list;
}
}
return ret;
}
const char* wolfSSH_GetAlgoListKex(WOLFSSH* ssh)
{
const char* list = NULL;
if (ssh) {
list = ssh->algoListKex;
}
return list;
}
int wolfSSH_CTX_SetAlgoListKey(WOLFSSH_CTX* ctx, const char* list)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx) {
/* NULL restores the server's auto-derived list; a client has none. */
if (list == NULL) {
ret = (ctx->side == WOLFSSH_ENDPOINT_SERVER) ?
WS_SUCCESS : WS_INVALID_ALGO_ID;
}
else {
ret = CheckAlgoList(list, TYPE_KEY);
}
if (ret == WS_SUCCESS) {
ctx->algoListKey = list;
}
}
return ret;
}
const char* wolfSSH_CTX_GetAlgoListKey(WOLFSSH_CTX* ctx)
{
const char* list = NULL;
if (ctx) {
list = ctx->algoListKey;
}
return list;
}
int wolfSSH_SetAlgoListKey(WOLFSSH* ssh, const char* list)
{
int ret = WS_SSH_NULL_E;
if (ssh) {
/* NULL is server-only. */
if (list == NULL) {
ret = (ssh->ctx != NULL
&& ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) ?
WS_SUCCESS : WS_INVALID_ALGO_ID;
}
else {
ret = CheckAlgoList(list, TYPE_KEY);
}
if (ret == WS_SUCCESS) {
ssh->algoListKey = list;
}
}
return ret;
}
const char* wolfSSH_GetAlgoListKey(WOLFSSH* ssh)
{
const char* list = NULL;
if (ssh) {
list = ssh->algoListKey;
}
return list;
}
int wolfSSH_CTX_SetAlgoListCipher(WOLFSSH_CTX* ctx, const char* list)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx) {
ret = CheckAlgoList(list, TYPE_CIPHER);
if (ret == WS_SUCCESS) {
ctx->algoListCipher = list;
}
}
return ret;
}
const char* wolfSSH_CTX_GetAlgoListCipher(WOLFSSH_CTX* ctx)
{
const char* list = NULL;
if (ctx) {
list = ctx->algoListCipher;
}
return list;
}
int wolfSSH_SetAlgoListCipher(WOLFSSH* ssh, const char* list)
{
int ret = WS_SSH_NULL_E;
if (ssh) {
ret = CheckAlgoList(list, TYPE_CIPHER);
if (ret == WS_SUCCESS) {
ssh->algoListCipher = list;
}
}
return ret;
}
const char* wolfSSH_GetAlgoListCipher(WOLFSSH* ssh)
{
const char* list = NULL;
if (ssh) {
list = ssh->algoListCipher;
}
return list;
}
int wolfSSH_CTX_SetAlgoListMac(WOLFSSH_CTX* ctx, const char* list)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx) {
ret = CheckAlgoList(list, TYPE_MAC);
if (ret == WS_SUCCESS) {
ctx->algoListMac = list;
}
}
return ret;
}
const char* wolfSSH_CTX_GetAlgoListMac(WOLFSSH_CTX* ctx)
{
const char* list = NULL;
if (ctx) {
list = ctx->algoListMac;
}
return list;
}
int wolfSSH_SetAlgoListMac(WOLFSSH* ssh, const char* list)
{
int ret = WS_SSH_NULL_E;
if (ssh) {
ret = CheckAlgoList(list, TYPE_MAC);
if (ret == WS_SUCCESS) {
ssh->algoListMac = list;
}
}
return ret;
}
const char* wolfSSH_GetAlgoListMac(WOLFSSH* ssh)
{
const char* list = NULL;
if (ssh) {
list = ssh->algoListMac;
}
return list;
}
int wolfSSH_CTX_SetAlgoListKeyAccepted(WOLFSSH_CTX* ctx, const char* list)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx) {
/* NULL empties the advertised list, it does not restore a default. */
ret = (list == NULL) ? WS_SUCCESS : CheckAlgoList(list, TYPE_KEY);
if (ret == WS_SUCCESS) {
ctx->algoListKeyAccepted = list;
}
}
return ret;
}
const char* wolfSSH_CTX_GetAlgoListKeyAccepted(WOLFSSH_CTX* ctx)
{
const char* list = NULL;
if (ctx) {
list = ctx->algoListKeyAccepted;
}
return list;
}
int wolfSSH_SetAlgoListKeyAccepted(WOLFSSH* ssh, const char* list)
{
int ret = WS_SSH_NULL_E;
if (ssh) {
/* NULL empties the advertised list, it does not restore a default. */
ret = (list == NULL) ? WS_SUCCESS : CheckAlgoList(list, TYPE_KEY);
if (ret == WS_SUCCESS) {
ssh->algoListKeyAccepted = list;
}
}
return ret;
}
const char* wolfSSH_GetAlgoListKeyAccepted(WOLFSSH* ssh)
{
const char* list = NULL;
if (ssh) {
list = ssh->algoListKeyAccepted;
}
return list;
}
int wolfSSH_CheckAlgoName(const char* name)
{
int ret = WS_INVALID_ALGO_ID;
if (name) {
word32 nameSz = (word32)WSTRLEN(name);
if (NameToId(name, nameSz) != ID_UNKNOWN) {
ret = WS_SUCCESS;
}
}
return ret;
}
const char* wolfSSH_QueryKex(word32* idx)
{
return NameByIndexType(TYPE_KEX, idx);
}
const char* wolfSSH_QueryKey(word32* idx)
{
return NameByIndexType(TYPE_KEY, idx);
}
const char* wolfSSH_QueryCipher(word32* idx)
{
return NameByIndexType(TYPE_CIPHER, idx);
}
const char* wolfSSH_QueryMac(word32* idx)
{
return NameByIndexType(TYPE_MAC, idx);
}
int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx,
const char* newBanner)
{
word32 newBannerSz = 0;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetBanner()");
if (ctx == NULL)
return WS_BAD_ARGUMENT;
if (newBanner != NULL) {
WLOG(WS_LOG_INFO, " setting banner to: \"%s\"", newBanner);
newBannerSz = (word32)WSTRLEN(newBanner);
}
ctx->banner = newBanner;
ctx->bannerSz = newBannerSz;
return WS_SUCCESS;
}
int wolfSSH_CTX_SetMaxAuthAttempts(WOLFSSH_CTX* ctx, int value)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetMaxAuthAttempts()");
if (ctx == NULL)
return WS_BAD_ARGUMENT;
/* A value <= 0 restores the built-in default. */
if (value <= 0)
ctx->maxAuthAttempts = DEFAULT_MAX_AUTH_ATTEMPTS;
else
ctx->maxAuthAttempts = (word32)value;
return WS_SUCCESS;
}
int wolfSSH_CTX_GetMaxAuthAttempts(WOLFSSH_CTX* ctx)
{
return (ctx == NULL) ? WS_BAD_ARGUMENT : (int)ctx->maxAuthAttempts;
}
int wolfSSH_SetMaxAuthAttempts(WOLFSSH* ssh, int value)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetMaxAuthAttempts()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
/* A value <= 0 restores the built-in default. */
if (value <= 0)
ssh->maxAuthAttempts = DEFAULT_MAX_AUTH_ATTEMPTS;
else
ssh->maxAuthAttempts = (word32)value;
return WS_SUCCESS;
}
int wolfSSH_GetMaxAuthAttempts(WOLFSSH* ssh)
{
return (ssh == NULL) ? WS_BAD_ARGUMENT : (int)ssh->maxAuthAttempts;
}
int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
const char* protoIdStr)
{
word32 protoIdStrSz;
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetSshProtoIdStr()");
if (!ctx || !protoIdStr) {
return WS_BAD_ARGUMENT;
}
protoIdStrSz = (word32)WSTRLEN(protoIdStr);
ret = ValidateProtoId(protoIdStr, protoIdStrSz);
if (ret != WS_SUCCESS) {
return ret;
}
ctx->sshProtoIdStr = protoIdStr;
ctx->sshProtoIdStrSz = protoIdStrSz;
return WS_SUCCESS;
}
int wolfSSH_CTX_UsePrivateKey_buffer(WOLFSSH_CTX* ctx,
const byte* in, word32 inSz, int format)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UsePrivateKey_buffer()");
ret = wolfSSH_ProcessBuffer(ctx, in, inSz, format, BUFTYPE_PRIVKEY);
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_CTX_UsePrivateKey_buffer(), ret = %d", ret);
return ret;
}
#ifdef WOLFSSH_CERTS
/* load in a X509 certificate that has public key to use
* return WS_SUCCESS on success
*/
int wolfSSH_CTX_UseCert_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UseCert_buffer()");
ret = wolfSSH_ProcessBuffer(ctx, cert, certSz, format, BUFTYPE_CERT);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_CTX_UseCert_buffer(), ret = %d", ret);
return ret;
}
/* Add a CA for verifying the peer's certificate with.
* returns WS_SUCCESS on success
*/
int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_AddRootCert_buffer()");
ret = wolfSSH_ProcessBuffer(ctx, cert, certSz, format, BUFTYPE_CA);
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_CTX_AddRootCert_buffer(), ret = %d", ret);
return ret;
}
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* Reads a certificate file and hands the PEM or DER it holds to the CTX. */
static int UseCertFile(WOLFSSH_CTX* ctx, const char* name, int type)
{
byte* in;
word32 inSz;
byte certId = ID_UNKNOWN;
byte flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
int format;
int ret;
if (ctx == NULL || name == NULL) {
return WS_BAD_ARGUMENT;
}
ret = ReadFileIntoBuffer(name, &in, &inSz, ctx->heap);
if (ret == WS_SUCCESS) {
format = SniffCertForm(in, inSz, &flavor, &certId);
if (format < 0) {
ret = format;
}
else if (format == WOLFSSH_FORMAT_SSH) {
/* A CTX takes the certificate itself, not a public key line. */
WLOG(WS_LOG_DEBUG, "Certificate file is not PEM or DER");
ret = WS_BAD_FILETYPE_E;
}
else {
ret = wolfSSH_ProcessBuffer(ctx, in, inSz, format, type);
}
WS_FORCEZERO(in, inSz);
WFREE(in, ctx->heap, DYNTYPE_FILE);
}
return ret;
}
/* Load in a X509 certificate file that has public key to use
* return WS_SUCCESS on success
*/
int wolfSSH_CTX_UseCert_file(WOLFSSH_CTX* ctx, const char* name)
{
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UseCert_file()");
ret = UseCertFile(ctx, name, BUFTYPE_CERT);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_CTX_UseCert_file(), ret = %d", ret);
return ret;
}
/* Add a CA file for verifying the peer's certificate with.
* returns WS_SUCCESS on success
*/
int wolfSSH_CTX_AddRootCert_file(WOLFSSH_CTX* ctx, const char* name)
{
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_AddRootCert_file()");
ret = UseCertFile(ctx, name, BUFTYPE_CA);
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_CTX_AddRootCert_file(), ret = %d", ret);
return ret;
}
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM */
#ifdef WOLFSSH_WINDOWS_CERT_STORE
/* Result of CertKeyCanSign(): whether the certificate's private key can
* actually be used for signing. NONE and NOSIGN are both unusable, but are
* kept distinct so the final diagnostic can tell an inaccessible key (fix
* the ACL) from a key enrolled without signing usage (enroll a signing
* certificate). */
#define WS_CERT_KEY_NONE 0 /* key not acquirable (missing, ACL, CSP) */
#define WS_CERT_KEY_UNKNOWN 1 /* key acquired but usage is not reported */
#define WS_CERT_KEY_SIGNS 2 /* key acquired and reports signing usage */
#define WS_CERT_KEY_NOSIGN 3 /* key acquired, usage excludes signing */
/* Classify the certificate's private key for signing use.
* CERT_KEY_PROV_INFO_PROP_ID is not enough: it is also set for legacy
* CryptoAPI/CSP keys, which CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG rejects.
* Use the same acquisition the signing path performs so a candidate that
* cannot sign is not chosen. Several smart-card / third-party KSPs do not
* implement NCRYPT_KEY_USAGE_PROPERTY; those keys are reported as
* usage-unknown so the caller can rank them below a key that definitely
* signs instead of failing open on the first one enumerated. */
static int CertKeyCanSign(PCCERT_CONTEXT pCertContext)
{
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hKey = 0;
DWORD dwKeySpec = 0;
BOOL fCallerFree = FALSE;
DWORD keyUsage = 0;
DWORD cbOut = 0;
SECURITY_STATUS status;
int canSign = WS_CERT_KEY_SIGNS;
if (!CryptAcquireCertificatePrivateKey(pCertContext,
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG,
NULL, &hKey, &dwKeySpec, &fCallerFree)) {
return WS_CERT_KEY_NONE;
}
/* CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG means dwKeySpec is documented to
* always be CERT_NCRYPT_KEY_SPEC; verify before handing the union-typed
* handle to CNG. A legacy CSP key cannot sign through NCryptSignHash()
* later, so reject it here rather than misreport it as usable. */
if (dwKeySpec != CERT_NCRYPT_KEY_SPEC) {
if (fCallerFree) {
/* a CSP handle is not an NCRYPT object; release it as a CSP */
CryptReleaseContext(hKey, 0);
}
return WS_CERT_KEY_NONE;
}
/* An acquirable key is not necessarily a signing key: an enterprise My
* store commonly holds a keyEncipherment-only RSA certificate next to
* the signing one with the same CN. Require NCRYPT_ALLOW_SIGNING_FLAG
* when the provider exposes the usage property. */
status = NCryptGetProperty(hKey, NCRYPT_KEY_USAGE_PROPERTY,
(PBYTE)&keyUsage, (DWORD)sizeof(keyUsage), &cbOut, 0);
if (status == 0 && cbOut == (DWORD)sizeof(keyUsage)) {
if ((keyUsage & NCRYPT_ALLOW_SIGNING_FLAG) == 0) {
WLOG(WS_LOG_DEBUG, "CertKeyCanSign: key usage 0x%lx does not "
"allow signing", (unsigned long)keyUsage);
canSign = WS_CERT_KEY_NOSIGN;
}
}
else {
WLOG(WS_LOG_DEBUG, "CertKeyCanSign: NCRYPT_KEY_USAGE_PROPERTY not "
"readable (status 0x%lx), key usage unknown",
(unsigned long)status);
canSign = WS_CERT_KEY_UNKNOWN;
}
if (fCallerFree) {
/* Only NCRYPT keys reach this point; a CSP key was released and
* rejected right after acquisition. */
NCryptFreeObject(hKey);
}
return canSign;
}
/* Find the certificate in hStore whose Common Name matches subjectName.
* subjectName may include a leading "CN=" prefix.
* CERT_FIND_SUBJECT_STR_W is only used as a substring pre-filter to
* enumerate candidates; each candidate's CN is then compared in full so
* that a lookup for "server1" does not select "server1.example" or
* "myserver1". The compare is ordinal and case insensitive, so it cannot
* change with the thread locale. Candidates are ranked by how usable they
* are: time-valid whose key definitely signs first, then time-valid whose
* provider does not report key usage, then (only when
* WOLFSSH_CERT_STORE_ALLOW_EXPIRED is defined) expired over not yet
* valid, then the latest NotAfter. Without that opt-in, a match that is
* not time-valid fails with WS_CERT_EXPIRED_E rather than silently
* presenting an expired credential. A candidate with no usable key is
* never selected -- the caller repeats the same key acquisition and would
* only fail with a misleading error -- so a public-only duplicate neither
* ends the search nor is returned. The selected certificate is stored in
* out, and is NULL when no match exists. The caller frees it with
* CertFreeCertificateContext.
* Returns WS_SUCCESS on success, WS_CRYPTO_FAILED when the CN matched
* only certificates whose private key is not accessible. */
static int FindCertByExactCN(void* heap, HCERTSTORE hStore,
const wchar_t* subjectName, PCCERT_CONTEXT* out)
{
PCCERT_CONTEXT pCertContext;
PCCERT_CONTEXT keyedMatch;
PCCERT_CONTEXT validUnknown;
const wchar_t* cn;
wchar_t* certCn;
DWORD certCnSz;
int match;
int hasKey;
int timeValidity;
int keyedEarly;
int keyedSigns;
int keylessSeen;
int nosignSeen;
int better;
int ret;
*out = NULL;
ret = WS_SUCCESS;
keyedEarly = 0;
keyedSigns = 0;
keylessSeen = 0;
nosignSeen = 0;
validUnknown = NULL;
/* Strip an optional "CN=" prefix from the requested name. */
cn = subjectName;
if (wcslen(cn) >= 3 &&
CompareStringOrdinal(cn, 3, L"CN=", 3, TRUE) == CSTR_EQUAL) {
cn = cn + 3;
}
if (*cn == L'\0') {
WLOG(WS_LOG_ERROR, "FindCertByExactCN: Empty common name requested");
return WS_BAD_ARGUMENT;
}
pCertContext = NULL;
keyedMatch = NULL;
for (;;) {
/* Passing the previous context frees it and continues the search. */
pCertContext = CertFindCertificateInStore(hStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0, CERT_FIND_SUBJECT_STR_W, cn, pCertContext);
if (pCertContext == NULL) {
break;
}
certCnSz = CertGetNameStringW(pCertContext, CERT_NAME_ATTR_TYPE, 0,
(void*)szOID_COMMON_NAME, NULL, 0);
if (certCnSz <= 1) {
continue;
}
certCn = (wchar_t*)WMALLOC(certCnSz * sizeof(wchar_t), heap,
DYNTYPE_TEMP);
if (certCn == NULL) {
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
ret = WS_MEMORY_E;
break;
}
certCnSz = CertGetNameStringW(pCertContext, CERT_NAME_ATTR_TYPE, 0,
(void*)szOID_COMMON_NAME, certCn, certCnSz);
match = (certCnSz > 1 &&
CompareStringOrdinal(certCn, -1, cn, -1, TRUE) == CSTR_EQUAL);
WFREE(certCn, heap, DYNTYPE_TEMP);
if (!match) {
continue;
}
/* A duplicate that cannot sign must not end the search.
* CertVerifyTimeValidity returns -1 before the validity period and
* +1 after it. */
hasKey = CertKeyCanSign(pCertContext);
timeValidity = CertVerifyTimeValidity(NULL, pCertContext->pCertInfo);
if (hasKey == WS_CERT_KEY_SIGNS && timeValidity == 0) {
break;
}
if (hasKey == WS_CERT_KEY_UNKNOWN && timeValidity == 0) {
/* Time-valid but the provider does not report key usage: keep
* the first one as a candidate and keep searching for a
* sibling that definitely signs, so an encryption-only key in
* a usage-silent KSP cannot shadow the signing one. */
if (validUnknown == NULL) {
validUnknown = CertDuplicateCertificateContext(pCertContext);
if (validUnknown == NULL) {
ret = WS_MEMORY_E;
}
}
}
else if (hasKey == WS_CERT_KEY_SIGNS ||
hasKey == WS_CERT_KEY_UNKNOWN) {
/* Deterministic fallback ranking, matching the time-valid path:
* a key that definitely signs beats one whose usage is unknown,
* then expired beats not yet valid, and within the same class
* the latest NotAfter wins. */
better = 0;
if (keyedMatch == NULL) {
better = 1;
}
else if (keyedSigns != (hasKey == WS_CERT_KEY_SIGNS)) {
better = (hasKey == WS_CERT_KEY_SIGNS);
}
else if (keyedEarly && timeValidity > 0) {
better = 1;
}
else if (keyedEarly == (timeValidity < 0) &&
CompareFileTime(&pCertContext->pCertInfo->NotAfter,
&keyedMatch->pCertInfo->NotAfter) > 0) {
better = 1;
}
if (better) {
if (keyedMatch != NULL) {
CertFreeCertificateContext(keyedMatch);
}
keyedMatch = CertDuplicateCertificateContext(pCertContext);
keyedEarly = (timeValidity < 0);
keyedSigns = (hasKey == WS_CERT_KEY_SIGNS);
if (keyedMatch == NULL) {
ret = WS_MEMORY_E;
}
}
}
else if (hasKey == WS_CERT_KEY_NOSIGN) {
nosignSeen = 1;
}
else {
keylessSeen = 1;
}
if (ret != WS_SUCCESS) {
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
break;
}
}
/* An allocation failure is reported as such rather than falling back
* to a candidate the enumeration had already rejected. */
if (ret == WS_SUCCESS && pCertContext == NULL) {
if (validUnknown != NULL) {
WLOG(WS_LOG_INFO, "FindCertByExactCN: No candidate reports "
"signing usage; using a time-valid '%ls' whose key "
"usage is unknown", subjectName);
pCertContext = validUnknown;
validUnknown = NULL;
}
else if (keyedMatch != NULL) {
#ifdef WOLFSSH_CERT_STORE_ALLOW_EXPIRED
/* WS_LOG_ERROR so the fallback is as visible as logging in
* this build allows; still only compiled in when logging is. */
WLOG(WS_LOG_ERROR, "FindCertByExactCN: No time-valid match, using "
"a %s '%ls' that has a private key",
keyedEarly ? "not yet valid" : "expired", subjectName);
pCertContext = keyedMatch;
keyedMatch = NULL;
#else
/* Fail closed by default: in a non-logging build a silent
* fallback would present an expired host credential with
* WS_SUCCESS and no local indication of the cause. Define
* WOLFSSH_CERT_STORE_ALLOW_EXPIRED to opt into the old
* behavior. */
WLOG(WS_LOG_ERROR, "FindCertByExactCN: '%ls' matched only a %s "
"certificate; rejecting (define "
"WOLFSSH_CERT_STORE_ALLOW_EXPIRED to use it anyway)",
subjectName, keyedEarly ? "not yet valid" : "expired");
ret = WS_CERT_EXPIRED_E;
#endif
}
else if (keylessSeen) {
WLOG(WS_LOG_ERROR, "FindCertByExactCN: '%ls' matched only "
"certificates with no usable private key", subjectName);
ret = WS_CRYPTO_FAILED;
}
else if (nosignSeen) {
WLOG(WS_LOG_ERROR, "FindCertByExactCN: '%ls' matched only "
"certificates whose private key is not a signing key; "
"enroll a signing certificate", subjectName);
ret = WS_CRYPTO_FAILED;
}
}
if (keyedMatch != NULL) {
CertFreeCertificateContext(keyedMatch);
}
if (validUnknown != NULL) {
CertFreeCertificateContext(validUnknown);
}
if (ret != WS_SUCCESS) {
if (pCertContext != NULL) {
CertFreeCertificateContext(pCertContext);
}
WLOG(WS_LOG_ERROR, "FindCertByExactCN: Failed, ret = %d", ret);
}
else {
*out = pCertContext;
}
return ret;
}
/* Resources for one cert-store backed private key slot. Everything is
* allocated before any slot is modified so that registering the plain key
* type and the matching X.509 type is all or nothing. */
typedef struct CertStoreSlot {
PCCERT_CONTEXT context;
byte* cert;
word32 certSz;
word32 keyIdx;
byte keyId;
} CertStoreSlot;
/* Release resources of a slot that was prepared but never committed. */
static void FreeCertStoreSlot(void* heap, CertStoreSlot* slot)
{
if (slot->context != NULL) {
CertFreeCertificateContext(slot->context);
}
if (slot->cert != NULL) {
WFREE(slot->cert, heap, DYNTYPE_CERT);
}
WMEMSET(slot, 0, sizeof(*slot));
}
/* Allocate the resources slot keyIdx needs, without modifying the
* context. The slot takes its own reference on pCertContext and its own
* copy of the certificate DER so that every slot can be freed
* independently by CtxResourceFree.
* Returns WS_SUCCESS on success. */
static int PrepCertStoreSlot(void* heap, byte keyId, word32 keyIdx,
PCCERT_CONTEXT pCertContext, CertStoreSlot* slot)
{
/* Validating the index here, before anything is mutated, is what lets
* CommitCertStoreSlot() be infallible. */
if (keyIdx >= WOLFSSH_MAX_PVT_KEYS) {
WLOG(WS_LOG_ERROR, "PrepCertStoreSlot: Slot index out of range");
return WS_BAD_ARGUMENT;
}
/* A zero-length certificate would be committed to the slot and later
* advertised as an x509v3 host key with an empty K_S. */
if (pCertContext->pbCertEncoded == NULL
|| pCertContext->cbCertEncoded == 0) {
WLOG(WS_LOG_ERROR, "PrepCertStoreSlot: Store certificate is empty");
return WS_BAD_ARGUMENT;
}
WMEMSET(slot, 0, sizeof(*slot));
slot->keyId = keyId;
slot->keyIdx = keyIdx;
slot->certSz = pCertContext->cbCertEncoded;
slot->cert = (byte*)WMALLOC(slot->certSz, heap, DYNTYPE_CERT);
slot->context = CertDuplicateCertificateContext(pCertContext);
if (slot->cert == NULL || slot->context == NULL) {
FreeCertStoreSlot(heap, slot);
WLOG(WS_LOG_ERROR, "PrepCertStoreSlot: Memory allocation failed");
return WS_MEMORY_E;
}
WMEMCPY(slot->cert, pCertContext->pbCertEncoded, slot->certSz);
return WS_SUCCESS;
}
/* Move the prepared resources into the context. The slot may previously
* have held either a cert-store key or a file-based key/cert, so clear
* both kinds of resources. Infallible by design: PrepCertStoreSlot()
* validates the slot index before anything is prepared, which is what
* makes the two-slot commit in wolfSSH_CTX_UsePrivateKey_fromStore()
* all-or-nothing. */
static void CommitCertStoreSlot(WOLFSSH_CTX* ctx, CertStoreSlot* slot)
{
WOLFSSH_PVT_KEY* pvtKey;
void* heap;
heap = ctx->heap;
pvtKey = &ctx->privateKey[slot->keyIdx];
if (pvtKey->certStoreContext != NULL) {
CertFreeCertificateContext(
(PCCERT_CONTEXT)pvtKey->certStoreContext);
}
if (pvtKey->key != NULL) {
/* Defensive only: wolfSSH_CTX_UsePrivateKey_fromStore() rejects a
* slot holding file-based credentials before preparing it, the
* same way SetHostPrivateKey() rejects the mirror-image order. */
WLOG(WS_LOG_ERROR, "CommitCertStoreSlot: Replacing the file-based "
"host key for this algorithm with the certificate store key");
WS_FORCEZERO(pvtKey->key, pvtKey->keySz);
WFREE(pvtKey->key, heap, DYNTYPE_PRIVKEY);
pvtKey->key = NULL;
pvtKey->keySz = 0;
}
if (pvtKey->cert != NULL) {
WFREE(pvtKey->cert, heap, DYNTYPE_CERT);
}
pvtKey->publicKeyFmt = slot->keyId;
#ifdef WOLFSSH_TPM
/* A stale TPM mark would route signing through the TPM. */
pvtKey->isTpm = 0;
#endif
pvtKey->useCertStore = 1;
pvtKey->certStoreContext = (void*)slot->context;
pvtKey->cert = slot->cert;
pvtKey->certSz = slot->certSz;
/* Ownership moved to the context. */
WMEMSET(slot, 0, sizeof(*slot));
}
#ifndef WOLFSSH_NO_ECDSA
/* DER-encoded named-curve OIDs as they appear in a certificate's
* SubjectPublicKeyInfo algorithm parameters. */
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
static const byte certStoreOidP256[] = {
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07
};
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384
static const byte certStoreOidP384[] = {
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22
};
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
static const byte certStoreOidP521[] = {
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23
};
#endif
#endif /* WOLFSSH_NO_ECDSA */
/* Load a private key from MS Certificate Store
* storeName: Certificate store name (e.g., L"My", L"Root")
* dwFlags: Certificate store location, and only a location (e.g.
* CERT_SYSTEM_STORE_CURRENT_USER). Control flags such as
* CERT_STORE_DELETE_FLAG would make CertOpenStore destructive and are
* rejected. The store is opened read-only.
* subjectName: Certificate subject Common Name for lookup, with or without
* a "CN=" prefix. The CN must match in full, case insensitively;
* thumbprint lookup is not currently implemented.
* The key is registered both as its plain key type and, mirroring the
* file-based HostKey plus HostCertificate pairing, as the matching
* RFC6187 x509v3-* type so the store certificate itself can be sent as
* the public host key to peers that negotiate certificate algorithms.
* The x509v3-* registration is skipped when the build compiles out the
* x509v3 algorithm for the key type (WOLFSSH_NO_SSH_RSA_SHA1, per-curve
* ECDSA gates) and for RSA when SHA-1 is soft disabled, since
* x509v3-ssh-rsa signs with SHA-1 and is then never advertised.
* returns WS_SUCCESS on success
*/
int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
const wchar_t* storeName, word32 dwFlags,
const wchar_t* subjectName)
{
int ret = WS_SUCCESS;
HCERTSTORE hStore = NULL;
PCCERT_CONTEXT pCertContext = NULL;
byte keyId = ID_NONE;
byte certId = ID_NONE;
PCERT_PUBLIC_KEY_INFO pPubKeyInfo = NULL;
CertStoreSlot keySlot;
CertStoreSlot certSlot;
word32 keyIdx;
word32 certIdx;
word32 newCount;
byte haveCertSlot;
#ifndef WOLFSSH_NO_ECDSA
const byte* params = NULL;
DWORD paramsSz = 0;
#endif
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UsePrivateKey_fromStore()");
if (ctx == NULL || storeName == NULL || subjectName == NULL) {
WLOG(WS_LOG_DEBUG, "wolfSSH_CTX_UsePrivateKey_fromStore: Bad argument");
return WS_BAD_ARGUMENT;
}
/* Only accept an assigned system-store location. Anything else is
* either not a location or a control flag (e.g. CERT_STORE_DELETE_FLAG)
* that would make CertOpenStore destructive. */
if (!wolfSSH_CertStoreLocationValid(dwFlags)) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: Store "
"flags are not a system store location");
return WS_BAD_ARGUMENT;
}
/* Open the certificate store. Read-only, both because nothing here
* writes to it and because a read/write open of a LOCAL_MACHINE store
* fails for a non-administrator service account. */
hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, (HCRYPTPROV_LEGACY)0,
(DWORD)dwFlags | CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG, storeName);
if (hStore == NULL) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: Failed to "
"open store, error: %lu", (unsigned long)GetLastError());
return WS_BAD_FILE_E;
}
/* Find the certificate by full Common Name match. */
ret = FindCertByExactCN(ctx->heap, hStore, subjectName, &pCertContext);
if (ret == WS_CRYPTO_FAILED) {
CertCloseStore(hStore, 0);
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: Certificate "
"matched but its private key is not usable for signing; see "
"the FindCertByExactCN message above for whether the key is "
"inaccessible (check key permissions for the service account) "
"or enrolled without signing usage");
return WS_CRYPTO_FAILED;
}
if (ret != WS_SUCCESS) {
CertCloseStore(hStore, 0);
return ret;
}
if (pCertContext == NULL) {
CertCloseStore(hStore, 0);
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: Certificate "
"not found with subject '%ls'", subjectName);
return WS_FATAL_ERROR;
}
/* Determine key type from certificate */
/* Get the public key info to determine algorithm */
pPubKeyInfo = &pCertContext->pCertInfo->SubjectPublicKeyInfo;
/* Check algorithm OID to determine key type. Only algorithms and
* curves compiled into this build are accepted; anything else leaves
* keyId as ID_NONE and is rejected below rather than registering a
* host key type that cannot be used for signing. */
if (pPubKeyInfo->Algorithm.pszObjId != NULL) {
/* Compare OID strings (they are ASCII, not wide) */
if (WSTRCMP(pPubKeyInfo->Algorithm.pszObjId, szOID_RSA_RSA) == 0) {
/* An RSA slot is useless without an RSA signature algorithm to
* negotiate, so require one the way wolfSSH_CTX_UseTpmHostKey does
* rather than consuming a slot RefreshPublicKeyAlgo will not
* advertise. */
#if !defined(WOLFSSH_NO_RSA) && \
(!defined(WOLFSSH_NO_RSA_SHA2_256) || \
!defined(WOLFSSH_NO_RSA_SHA2_512) || \
(defined(WOLFSSH_NO_SHA1_SOFT_DISABLE) && \
!defined(WOLFSSH_NO_SSH_RSA_SHA1)))
keyId = ID_SSH_RSA;
#else
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: "
"No usable RSA signature algorithm is compiled in");
#endif
}
else if (WSTRCMP(pPubKeyInfo->Algorithm.pszObjId,
szOID_ECC_PUBLIC_KEY) == 0) {
#ifndef WOLFSSH_NO_ECDSA
/* The algorithm parameters hold the DER-encoded named-curve
* OID; match its raw bytes to select the ECDSA key type. */
params = pPubKeyInfo->Algorithm.Parameters.pbData;
paramsSz = pPubKeyInfo->Algorithm.Parameters.cbData;
if (params == NULL) {
paramsSz = 0;
}
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
if (paramsSz == sizeof(certStoreOidP256) &&
WMEMCMP(params, certStoreOidP256,
sizeof(certStoreOidP256)) == 0) {
keyId = ID_ECDSA_SHA2_NISTP256;
}
else
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384
if (paramsSz == sizeof(certStoreOidP384) &&
WMEMCMP(params, certStoreOidP384,
sizeof(certStoreOidP384)) == 0) {
keyId = ID_ECDSA_SHA2_NISTP384;
}
else
#endif
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521
if (paramsSz == sizeof(certStoreOidP521) &&
WMEMCMP(params, certStoreOidP521,
sizeof(certStoreOidP521)) == 0) {
keyId = ID_ECDSA_SHA2_NISTP521;
}
else
#endif
{
/* With all curves disabled paramsSz is set but never read. */
WOLFSSH_UNUSED(paramsSz);
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: "
"Unsupported ECC curve parameters");
}
#else
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: "
"ECDSA is not compiled in");
#endif /* WOLFSSH_NO_ECDSA */
}
else {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: "
"Unsupported key algorithm: %s",
pPubKeyInfo->Algorithm.pszObjId);
}
}
else {
WLOG(WS_LOG_ERROR,
"wolfSSH_CTX_UsePrivateKey_fromStore: No algorithm OID");
}
if (keyId == ID_NONE) {
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
return WS_BAD_ARGUMENT;
}
/* FindCertByExactCN only returns a certificate whose private key
* CertKeyCanSign() could acquire, so key access is already verified. */
/* Register the key under its plain type so peers without RFC6187
* support get a raw public key, and under the matching X.509 type so
* the store certificate can be sent as K_S when a peer negotiates an
* x509v3-* algorithm. Both slots are located and prepared before
* either is committed, so a failure leaves the context, including any
* host key already in these slots, exactly as it was. */
WMEMSET(&keySlot, 0, sizeof(keySlot));
WMEMSET(&certSlot, 0, sizeof(certSlot));
newCount = ctx->privateKeyCount;
keyIdx = FindPvtKeyIdx(ctx, keyId);
/* Mirror of SetHostPrivateKey()/SetHostCertificate(): a store key must
* not silently replace file- or TPM-based credentials already loaded
* for this algorithm, so the mixed configuration is rejected in both
* load orders. Replacing a previous store key is still allowed. */
if (keyIdx != WOLFSSH_MAX_PVT_KEYS &&
!ctx->privateKey[keyIdx].useCertStore) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: A host key "
"for this algorithm is already loaded from a file or TPM; it "
"cannot be paired with a store key");
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
return WS_BAD_ARGUMENT;
}
if (keyIdx == WOLFSSH_MAX_PVT_KEYS) {
keyIdx = newCount++;
}
/* CertTypeForId returns keyId unchanged when the build has no X509
* equivalent; skip the X509 ID slot in that case. haveCertSlot rather
* than a certIdx sentinel, so the "skip" marker cannot be confused with
* an index a full table legitimately computes. */
certId = CertTypeForId(keyId);
#if !defined(WOLFSSH_NO_SHA1_SOFT_DISABLE)
/* x509v3-ssh-rsa signs with SHA-1; with SHA-1 soft disabled do not
* register a slot for it. Note the canned lists only gate the client
* default; a server would advertise the slot via RefreshPublicKeyAlgo,
* which is exactly why it must not be registered. If an earlier
* file-based load already claimed that slot, reject the mixed
* configuration outright rather than leaving a stale file key and
* certificate advertised beside the store key. */
if (certId == ID_X509V3_SSH_RSA) {
if (FindPvtKeyIdx(ctx, certId) != WOLFSSH_MAX_PVT_KEYS) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: An "
"x509v3-ssh-rsa file host key/certificate is already "
"loaded; it cannot be paired with a store key");
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
return WS_BAD_ARGUMENT;
}
certId = keyId;
}
#endif
certIdx = 0;
haveCertSlot = 0;
if (certId != keyId) {
certIdx = FindPvtKeyIdx(ctx, certId);
/* Same mixed-configuration rejection for the x509v3 slot, so a
* file HostCertificate is never silently destroyed either. */
if (certIdx != WOLFSSH_MAX_PVT_KEYS &&
!ctx->privateKey[certIdx].useCertStore) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: A host "
"certificate for this algorithm is already loaded from a "
"file; it cannot be paired with a store key");
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
return WS_BAD_ARGUMENT;
}
if (certIdx == WOLFSSH_MAX_PVT_KEYS) {
certIdx = newCount++;
}
haveCertSlot = 1;
}
else {
WLOG(WS_LOG_INFO, "wolfSSH_CTX_UsePrivateKey_fromStore: No x509v3 "
"algorithm for key type %d in this build, registering the "
"plain host key only", keyId);
}
if (newCount > WOLFSSH_MAX_PVT_KEYS) {
WLOG(WS_LOG_ERROR, "wolfSSH_CTX_UsePrivateKey_fromStore: Not enough "
"free key slots; a store key needs one for the plain type and "
"one for the x509v3 type");
ret = WS_CTX_KEY_COUNT_E;
}
if (ret == WS_SUCCESS) {
ret = PrepCertStoreSlot(ctx->heap, keyId, keyIdx, pCertContext,
&keySlot);
}
if (ret == WS_SUCCESS && haveCertSlot) {
ret = PrepCertStoreSlot(ctx->heap, certId, certIdx, pCertContext,
&certSlot);
}
if (ret == WS_SUCCESS) {
/* Committing cannot fail (see CommitCertStoreSlot), so once both
* slots are prepared the context update is atomic from the caller's
* point of view. */
CommitCertStoreSlot(ctx, &keySlot);
if (haveCertSlot) {
CommitCertStoreSlot(ctx, &certSlot);
}
ctx->privateKeyCount = newCount;
}
else {
FreeCertStoreSlot(ctx->heap, &keySlot);
FreeCertStoreSlot(ctx->heap, &certSlot);
}
/* Each registered slot holds its own reference on the certificate
* context for later signing operations, so release the lookup
* reference from CertFindCertificateInStore. Closing the store does
* not invalidate the slot contexts.
* Note: if the certificate is removed from the store while we hold
* these contexts, CryptAcquireCertificatePrivateKey may fail at
* signing time. */
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStore, 0);
if (ret == WS_SUCCESS) {
/* Refresh public key algorithm list */
RefreshPublicKeyAlgo(ctx);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_CTX_UsePrivateKey_fromStore(), "
"ret = %d", ret);
return ret;
}
/* Report the certificate a cert-store host key is bound to, so an
* application can offer it for certificate user auth without reaching into
* the private CTX layout. Returns the first cert-store backed slot
* registered under an x509v3 algorithm: cert/certSz point at the DER copy
* owned by the CTX (valid until the CTX is freed or the slot replaced) and
* algoName at the static SSH algorithm name string. Any out pointer may be
* NULL to skip it. Returns WS_SUCCESS, WS_BAD_ARGUMENT on a NULL ctx, or
* WS_FATAL_ERROR when no such slot exists (e.g. the x509v3 form of the key
* type is compiled out or soft disabled). */
int wolfSSH_CTX_GetCertStoreCert(WOLFSSH_CTX* ctx, const byte** cert,
word32* certSz, const char** algoName)
{
const WOLFSSH_PVT_KEY* pvtKey;
word32 i;
int isX509Id;
if (ctx == NULL) {
return WS_BAD_ARGUMENT;
}
for (i = 0; i < ctx->privateKeyCount && i < WOLFSSH_MAX_PVT_KEYS; i++) {
pvtKey = &ctx->privateKey[i];
if (!pvtKey->useCertStore || pvtKey->certStoreContext == NULL ||
pvtKey->key != NULL ||
pvtKey->cert == NULL || pvtKey->certSz == 0) {
continue;
}
switch (pvtKey->publicKeyFmt) {
case ID_X509V3_SSH_RSA:
case ID_X509V3_ECDSA_SHA2_NISTP256:
case ID_X509V3_ECDSA_SHA2_NISTP384:
case ID_X509V3_ECDSA_SHA2_NISTP521:
isX509Id = 1;
break;
default:
isX509Id = 0;
break;
}
if (!isX509Id) {
continue;
}
if (cert != NULL) {
*cert = pvtKey->cert;
}
if (certSz != NULL) {
*certSz = pvtKey->certSz;
}
if (algoName != NULL) {
*algoName = IdToName(pvtKey->publicKeyFmt);
}
return WS_SUCCESS;
}
return WS_FATAL_ERROR;
}
#endif /* WOLFSSH_WINDOWS_CERT_STORE */
#endif /* WOLFSSH_CERTS */
int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
word32 windowSz, word32 maxPacketSz)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetWindowPacketSize()");
if (ctx == NULL)
return WS_BAD_ARGUMENT;
if (windowSz != 0 && windowSz > WINDOW_SZ_UPPER_BOUND)
return WS_BAD_ARGUMENT;
if (windowSz == 0)
windowSz = DEFAULT_WINDOW_SZ;
if (maxPacketSz != 0 && maxPacketSz > MAX_CHANNEL_PACKET_SZ)
return WS_BAD_ARGUMENT;
if (maxPacketSz == 0)
maxPacketSz = DEFAULT_MAX_PACKET_SZ;
ctx->windowSz = windowSz;
ctx->maxPacketSz = maxPacketSz;
return WS_SUCCESS;
}
void wolfSSH_GetStats(WOLFSSH* ssh, word32* txCount, word32* rxCount,
word32* seq, word32* peerSeq)
{
word32 rTxCount = 0;
word32 rRxCount = 0;
word32 rSeq = 0;
word32 rPeerSeq = 0;
if (ssh != NULL) {
rTxCount = ssh->txCount;
rRxCount = ssh->rxCount;
rSeq = ssh->seq;
rPeerSeq = ssh->peerSeq;
}
if (txCount != NULL)
*txCount = rTxCount;
if (rxCount != NULL)
*rxCount = rRxCount;
if (seq != NULL)
*seq = rSeq;
if (peerSeq != NULL)
*peerSeq = rPeerSeq;
}
int wolfSSH_KDF(byte hashId, byte keyId,
byte* key, word32 keySz,
const byte* k, word32 kSz,
const byte* h, word32 hSz,
const byte* sessionId, word32 sessionIdSz)
{
int doKeyPadding = 1;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_KDF()");
return GenerateKey(hashId, keyId, key, keySz, k, kSz, h, hSz,
sessionId, sessionIdSz, doKeyPadding);
}
WS_SessionType wolfSSH_GetSessionType(const WOLFSSH* ssh)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetSessionType()");
if (ssh && ssh->channelList)
return (WS_SessionType)ssh->channelList->sessionType;
return WOLFSSH_SESSION_UNKNOWN;
}
const char* wolfSSH_GetSessionCommand(const WOLFSSH* ssh)
{
const char* cmd = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetSessionCommand()");
if (ssh) {
cmd = wolfSSH_ChannelGetSessionCommand(ssh->channelList);
}
return cmd;
}
word32 wolfSSH_GetSessionCommandSz(const WOLFSSH* ssh)
{
word32 commandSz = 0;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_GetSessionCommandSz()");
if (ssh) {
commandSz = wolfSSH_ChannelGetSessionCommandSz(ssh->channelList);
}
return commandSz;
}
int wolfSSH_worker(WOLFSSH* ssh, word32* channelId)
{
int ret = WS_SUCCESS;
int sendRet = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_worker()");
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
/* Nothing left to drive: no reply may go out and inbound messages are
* skipped, so every pass from here on would answer WS_SUCCESS off a
* dispatch that did nothing and a caller turning the crank would never
* see the session end. What arrived before the disconnect is still the
* caller's, through the read calls. RFC 4253 section 11.1. */
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) {
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_worker(), session disconnected");
return WS_FATAL_ERROR;
}
/* Always service inbound data first so window updates can unblock sends. */
if (ret == WS_SUCCESS) {
ret = DoReceive(ssh);
}
/* Flush queued output whatever DoReceive() made of the socket, since an
* idle receive reports WS_FATAL_ERROR. !ssh->disconnected gates it. */
if (wolfSSH_OutputPending(ssh) && !ssh->disconnected) {
int rxErr = ssh->error;
sendRet = wolfSSH_SendPacket(ssh);
if (sendRet != WS_SUCCESS) {
if (ret == WS_SUCCESS) {
ret = sendRet;
}
else if (sendRet != WS_WANT_WRITE && ret != WS_CHANNEL_CLOSED
&& ret != WS_FATAL_ERROR) {
/* The transport is gone, so the event it came with is moot. */
ret = sendRet;
}
else if ((ret == WS_CHANNEL_CLOSED && sendRet != WS_WANT_WRITE)
|| (ret == WS_FATAL_ERROR && rxErr != WS_WANT_READ)) {
/* A failed receive outranks the flush, and so does a close
* whose flush hard-failed: callers route teardown on it.
* Every other status keeps the code the send set. */
ssh->error = rxErr;
}
}
}
/* WS_EXTDATA and WS_EOF report the channel too, so a multi-channel caller
* can route the drain, or see which channel half-closed. */
if (ret == WS_SUCCESS || ret == WS_CHAN_RXD || ret == WS_EXTDATA
|| ret == WS_EOF) {
if (channelId != NULL) {
*channelId = ssh->lastRxId;
}
/* Report the rekey, unless it would hide a once-only WS_EXTDATA
* or WS_EOF, or the error from a flush that failed. */
if (ssh->isKeying && ret != WS_EXTDATA && ret != WS_EOF
&& sendRet == WS_SUCCESS) {
ssh->error = WS_REKEYING;
return WS_REKEYING;
}
}
if (ret == WS_CHAN_RXD) {
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_worker(), "
"data received on channel %u", ssh->lastRxId);
}
else {
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_worker(), ret = %d", ret);
}
return ret;
}
int wolfSSH_GetLastRxId(WOLFSSH* ssh, word32* channelId)
{
int ret = WS_SUCCESS;
if (ssh == NULL || channelId == NULL)
ret = WS_ERROR;
if (ret == WS_SUCCESS)
*channelId = ssh->lastRxId;
return ret;
}
int wolfSSH_OutputPending(const WOLFSSH* ssh)
{
return (ssh != NULL && ssh->outputBuffer.length > ssh->outputBuffer.idx);
}
#ifdef WOLFSSH_FWD
int wolfSSH_CTX_SetFwdCb(WOLFSSH_CTX* ctx,
WS_CallbackFwd fwdCb, WS_CallbackFwdIO fwdIoCb)
{
int ret = WS_SUCCESS;
if (ctx == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS) {
ctx->fwdCb = fwdCb;
ctx->fwdIoCb = fwdIoCb;
}
return ret;
}
int wolfSSH_SetFwdCbCtx(WOLFSSH* ssh, void* ctx)
{
int ret = WS_SUCCESS;
if (ssh == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS) {
ssh->fwdCbCtx = ctx;
}
return ret;
}
WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNewLocal(WOLFSSH* ssh,
const char* host, word32 hostPort,
const char* origin, word32 originPort)
{
WOLFSSH_CHANNEL* newChannel = NULL;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelFwdNewLocal()");
if (ssh == NULL || ssh->ctx == NULL || host == NULL || origin == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
newChannel = ChannelNew(ssh, ID_CHANTYPE_TCPIP_DIRECT,
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
if (newChannel == NULL)
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS)
ret = ChannelUpdateForward(newChannel,
host, hostPort, origin, originPort, 1);
if (ret == WS_SUCCESS)
ret = SendChannelOpenForward(ssh, newChannel);
if (ret != WS_SUCCESS) {
void* heap = (ssh != NULL && ssh->ctx != NULL) ? ssh->ctx->heap : NULL;
ChannelDelete(newChannel, heap);
newChannel = NULL;
}
if (newChannel != NULL)
ChannelAppend(ssh, newChannel);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelFwdNewLocal(), newChannel = %p",
newChannel);
return newChannel;
}
WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNewRemote(WOLFSSH* ssh,
const char* host, word32 hostPort,
const char* origin, word32 originPort)
{
WOLFSSH_CHANNEL* newChannel = NULL;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelFwdNewRemote()");
if (ssh == NULL || ssh->ctx == NULL || host == NULL || origin == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS) {
newChannel = ChannelNew(ssh, ID_CHANTYPE_TCPIP_FORWARD,
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
if (newChannel == NULL)
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS)
ret = ChannelUpdateForward(newChannel,
host, hostPort, origin, originPort, 0);
if (ret == WS_SUCCESS)
ret = SendChannelOpenForward(ssh, newChannel);
if (ret == WS_SUCCESS) {
if (ssh->ctx->fwdCb) {
ret = ssh->ctx->fwdCb(WOLFSSH_FWD_CHANNEL_ID, ssh->fwdCbCtx,
NULL, newChannel->channel);
}
}
if (ret != WS_SUCCESS) {
void* heap = (ssh != NULL && ssh->ctx != NULL) ? ssh->ctx->heap : NULL;
ChannelDelete(newChannel, heap);
newChannel = NULL;
}
if (newChannel != NULL)
ChannelAppend(ssh, newChannel);
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_ChannelFwdNewRemote(), newChannel = %p, ret = %d",
newChannel, ret);
return newChannel;
}
WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNew(WOLFSSH* ssh,
const char* host, word32 hostPort,
const char* origin, word32 originPort)
{
return wolfSSH_ChannelFwdNewLocal(ssh, host, hostPort, origin, originPort);
}
/* Send "tcpip-forward" and register the forward. See wolfssh/ssh.h for the
* matching rules and what a port-0 request needs. */
int wolfSSH_FwdRemoteSetup(WOLFSSH* ssh, const char* bindAddr,
word32 bindPort, int wantReply)
{
WOLFSSH_FWD_PENDING pend;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_FwdRemoteSetup()");
if (ssh == NULL || ssh->ctx == NULL || bindAddr == NULL)
ret = WS_BAD_ARGUMENT;
/* A port is 16 bits on the wire; 0 asks the peer to allocate one. */
if (ret == WS_SUCCESS && bindPort > 65535)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && wantReply != 0 && wantReply != 1)
ret = WS_BAD_ARGUMENT;
/* The peer's reply is the only place a port-0 request learns the port it
* got, and neither the caller nor the forwarded-tcpip check works without
* one. */
if (ret == WS_SUCCESS && bindPort == 0 && !wantReply)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && ssh->ctx->side != WOLFSSH_ENDPOINT_CLIENT)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
/* A global request must not go out mid-rekey; only KEX traffic may. */
if (ret == WS_SUCCESS && ssh->isKeying)
ret = WS_REKEYING;
/* Everything that can fail is allocated before the request goes out, so an
* error from here means the peer heard nothing. */
if (ret == WS_SUCCESS)
ret = FwdRemotePrepare(ssh, bindAddr, bindPort, wantReply, 0, &pend);
/* The send settles pend: what reached the peer registers, even when the
* post-send highwater callback reports an error afterwards. */
if (ret == WS_SUCCESS)
ret = SendGlobalRequestFwd(ssh, bindAddr, bindPort, 0, wantReply,
&pend);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_FwdRemoteSetup(), ret = %d", ret);
return ret;
}
/* Send "cancel-tcpip-forward", tearing down a wolfSSH_FwdRemoteSetup()
* listener. See wolfssh/ssh.h for when the registration actually drops. */
int wolfSSH_FwdRemoteCancel(WOLFSSH* ssh, const char* bindAddr,
word32 bindPort, int wantReply)
{
WOLFSSH_FWD_PENDING pend;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_FwdRemoteCancel()");
if (ssh == NULL || ssh->ctx == NULL || bindAddr == NULL)
ret = WS_BAD_ARGUMENT;
/* Unlike the setup, 0 names no listener the peer could have bound. */
if (ret == WS_SUCCESS && (bindPort == 0 || bindPort > 65535))
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && wantReply != 0 && wantReply != 1)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && ssh->ctx->side != WOLFSSH_ENDPOINT_CLIENT)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
ret = WS_FATAL_ERROR;
/* A global request must not go out mid-rekey; only KEX traffic may. */
if (ret == WS_SUCCESS && ssh->isKeying)
ret = WS_REKEYING;
if (ret == WS_SUCCESS)
ret = FwdRemotePrepare(ssh, bindAddr, bindPort, wantReply, 1, &pend);
if (ret == WS_SUCCESS)
ret = SendGlobalRequestFwd(ssh, bindAddr, bindPort, 1, wantReply,
&pend);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_FwdRemoteCancel(), ret = %d", ret);
return ret;
}
int wolfSSH_SetFwdRemoteMatch(WOLFSSH* ssh, byte match)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetFwdRemoteMatch()");
if (ssh == NULL || match > WOLFSSH_FWD_MATCH_OFF)
ret = WS_BAD_ARGUMENT;
else
ssh->fwdRemoteMatch = match;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_SetFwdRemoteMatch(), ret = %d", ret);
return ret;
}
#endif /* WOLFSSH_FWD */
int wolfSSH_ChannelFree(WOLFSSH_CHANNEL* channel)
{
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelFree()");
if (channel != NULL) {
ret = ChannelRemove(channel->ssh,
channel->channel, WS_CHANNEL_ID_SELF);
}
else
ret = WS_BAD_ARGUMENT;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelFree(), ret = %d", ret);
return ret;
}
int wolfSSH_ChannelGetId(WOLFSSH_CHANNEL* channel, word32* id, byte peer)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetId()");
if (channel == NULL || id == NULL)
ret = WS_BAD_ARGUMENT;
else {
*id = (peer == WS_CHANNEL_ID_SELF) ?
channel->channel : channel->peerChannel;
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelGetId(), ret = %d", ret);
return ret;
}
WOLFSSH_CHANNEL* wolfSSH_ChannelFind(WOLFSSH* ssh, word32 id, byte peer)
{
return ChannelFind(ssh, id, peer);
}
#ifdef WOLFSSH_FWD
int wolfSSH_ChannelSetFwdFd(WOLFSSH_CHANNEL* channel, int fwdFd)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelSetFwdFd()");
if (channel != NULL)
channel->fwdFd = fwdFd;
else
ret = WS_BAD_ARGUMENT;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelSetFwdFd(), ret = %d", ret);
return ret;
}
int wolfSSH_ChannelGetFwdFd(const WOLFSSH_CHANNEL* channel)
{
int fwdFd = -1;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetFwdFd()");
if (channel != NULL)
fwdFd = channel->fwdFd;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelGetFwdFd(), ret = %d", fwdFd);
return fwdFd;
}
#endif /* WOLFSSH_FWD */
/* moves the window for more room
* returns WS_SUCCESS on success */
static int _UpdateChannelWindow(WOLFSSH_CHANNEL* channel)
{
WOLFSSH_BUFFER* inputBuffer;
int sendResult = WS_SUCCESS;
if (channel == NULL)
return WS_BAD_ARGUMENT;
inputBuffer = &channel->inputBuffer;
if ((inputBuffer->length > inputBuffer->bufferSz / 2) ||
(channel->windowSz == 0)) {
word32 usedSz = inputBuffer->length - inputBuffer->idx;
word32 bytesToAdd = inputBuffer->idx;
WLOG(WS_LOG_DEBUG, "Making more room: %u", usedSz);
if (usedSz) {
WLOG(WS_LOG_DEBUG, " ...moving data down");
WMEMMOVE(inputBuffer->buffer,
inputBuffer->buffer + bytesToAdd, usedSz);
}
/* Credit through the channel, not a direct send: credit that cannot go
* out now is parked (mid-rekey per RFC 4253 section 7.1, or a failed
* send) and a zero-byte adjust is suppressed. Stderr charges the window
* without consuming this buffer, so windowSz == 0 above can fire with
* nothing read here. */
sendResult = ChannelCreditWindow(channel->ssh, channel, bytesToAdd);
WLOG(WS_LOG_INFO, " bytesToAdd = %u", bytesToAdd);
WLOG(WS_LOG_INFO, " windowSz = %u", channel->windowSz);
channel->windowSz += bytesToAdd;
WLOG(WS_LOG_INFO, " update windowSz = %u", channel->windowSz);
inputBuffer->length = usedSz;
inputBuffer->idx = 0;
}
return sendResult;
}
/* Drains buffered channel data and credits the window for the bytes taken.
* Reports the bytes copied; an adjust that could not go out lands in
* ssh->error. */
static int _ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
{
WOLFSSH_BUFFER* inputBuffer;
WOLFSSH* ssh;
word32 creditedSz;
int updateResult = WS_SUCCESS;
int savedError;
if (channel == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
ssh = channel->ssh;
inputBuffer = &channel->inputBuffer;
if (inputBuffer->idx > inputBuffer->length) {
WLOG(WS_LOG_ERROR, "Bad internal state for buffer index");
return WS_INVALID_STATE_E;
}
bufSz = min(bufSz, inputBuffer->length - inputBuffer->idx);
WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, bufSz);
inputBuffer->idx += bufSz;
/* Unguarded by bufSz: also compacts, and carries credit left behind. */
savedError = ssh->error;
creditedSz = inputBuffer->idx;
updateResult = _UpdateChannelWindow(channel);
if (updateResult == WS_SUCCESS) {
/* Clear the old WS_WANT_WRITE only if this read sent an adjust of
* its own and the output buffer is now empty. */
if (savedError == WS_WANT_WRITE && creditedSz != 0
&& inputBuffer->idx == 0 && ssh->outputBuffer.length == 0) {
ssh->error = WS_SUCCESS;
}
else {
ssh->error = savedError;
}
}
else {
/* The adjust can fail before it reaches the transport, so the code
* is recorded here; the log skips a WS_WANT_WRITE, which only asks
* for a retry. */
ssh->error = updateResult;
if (updateResult != WS_WANT_WRITE) {
WLOG(WS_LOG_ERROR,
"_ChannelRead: window adjust send failed (%d); read still "
"succeeded", updateResult);
}
}
return (int)bufSz;
}
/* Drains buffered extended data (stderr) and credits the channel window for the
* bytes taken, releasing the back-pressure DoChannelExtendedData() applied.
* Always reports the bytes copied, even when the window adjust cannot go out:
* they are already in the caller's buffer, and ChannelCreditWindow() keeps the
* credit owed. */
static int _ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
{
WOLFSSH_BUFFER* extDataBuffer;
WOLFSSH* ssh;
if (channel == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
ssh = channel->ssh;
extDataBuffer = &channel->extDataBuffer;
/* sanity check to make sure idx is not in a bad state */
if (extDataBuffer->idx > extDataBuffer->length) {
WLOG(WS_LOG_ERROR, "Bad internal state for buffer index");
return WS_INVALID_STATE_E;
}
bufSz = min(bufSz, extDataBuffer->length - extDataBuffer->idx);
WMEMCPY(buf, extDataBuffer->buffer + extDataBuffer->idx, bufSz);
extDataBuffer->idx += bufSz;
if (bufSz > 0) {
int adjustResult;
int savedError = ssh->error;
/* Credit locally regardless of the send result; ChannelCreditWindow()
* owns getting it to the peer. */
channel->windowSz += bufSz;
/* Fully drained: release the allocation. */
if (extDataBuffer->idx == extDataBuffer->length)
ShrinkBuffer(extDataBuffer, 0);
adjustResult = ChannelCreditWindow(ssh, channel, bufSz);
if (adjustResult == WS_SUCCESS) {
/* Don't restore an owed-flush status once the buffer has drained. */
if (savedError == WS_WANT_WRITE && ssh->outputBuffer.length == 0)
ssh->error = WS_SUCCESS;
else
ssh->error = savedError;
}
else {
/* The adjust can fail before it reaches the transport, so the
* code is recorded here; the log skips a WS_WANT_WRITE, which
* only asks for a retry. */
ssh->error = adjustResult;
if (adjustResult != WS_WANT_WRITE) {
WLOG(WS_LOG_ERROR,
"_ChannelReadExt: window adjust send failed (%d); read "
"still succeeded", adjustResult);
}
}
}
return (int)bufSz;
}
int wolfSSH_ChannelIdRead(WOLFSSH* ssh, word32 channelId,
byte* buf, word32 bufSz)
{
WOLFSSH_CHANNEL* channel = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelIdRead()");
if (ssh == NULL || buf == NULL)
return WS_BAD_ARGUMENT;
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL)
return WS_INVALID_CHANID;
bufSz = _ChannelRead(channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelIdRead(), rxd = %d", bufSz);
return bufSz;
}
int wolfSSH_ChannelRead(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelRead()");
if (channel == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
if (channel->ssh->isKeying) {
channel->ssh->error = WS_REKEYING;
return WS_REKEYING;
}
bufSz = _ChannelRead(channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelRead(), bytesRxd = %d",
bufSz);
return bufSz;
}
int wolfSSH_ChannelIdReadExt(WOLFSSH* ssh, word32 channelId,
byte* buf, word32 bufSz)
{
WOLFSSH_CHANNEL* channel = NULL;
int bytesRxd;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelIdReadExt(), ID = %u",
channelId);
if (ssh == NULL || buf == NULL)
return WS_BAD_ARGUMENT;
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL)
return WS_INVALID_CHANID;
bytesRxd = _ChannelReadExt(channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelIdReadExt(), rxd = %d",
bytesRxd);
return bytesRxd;
}
/* Unlike wolfSSH_ChannelRead(), this does not fail with WS_REKEYING while
* keying: the bytes are already buffered, and the window credit the read owes
* is parked until the rekey completes. Failing here would drop the data. */
int wolfSSH_ChannelReadExt(WOLFSSH_CHANNEL* channel, byte* buf, word32 bufSz)
{
int bytesRxd;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelReadExt()");
if (channel == NULL || buf == NULL || bufSz == 0)
return WS_BAD_ARGUMENT;
bytesRxd = _ChannelReadExt(channel, buf, bufSz);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelReadExt(), bytesRxd = %d",
bytesRxd);
return bytesRxd;
}
int wolfSSH_ChannelSend(WOLFSSH_CHANNEL* channel,
const byte* buf, word32 bufSz)
{
int bytesTxd = 0;
if (channel == NULL || buf == NULL) {
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelSend() with bad argument");
return WS_BAD_ARGUMENT;
}
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelSend(), ID = %d, peerID = %d",
channel->channel, channel->peerChannel);
if (channel->ssh != NULL && SendAfterDisconnect(channel->ssh))
return WS_FATAL_ERROR;
#ifdef DEBUG_WOLFSSH
DumpOctetString(buf, bufSz);
#endif
if (!channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
bytesTxd = WS_CHANNEL_NOT_CONF;
}
else {
WLOG(WS_LOG_DEBUG, "Sending data.");
bytesTxd = SendChannelData(channel->ssh, channel->channel,
(byte*)buf, bufSz);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelSend(), bytesTxd = %d",
bytesTxd);
return bytesTxd;
}
int wolfSSH_ChannelSendExt(WOLFSSH_CHANNEL* channel,
const byte* buf, word32 bufSz)
{
int bytesTxd = 0;
if (channel == NULL || buf == NULL) {
WLOG(WS_LOG_DEBUG,
"Entering wolfSSH_ChannelSendExt() with bad argument");
return WS_BAD_ARGUMENT;
}
WLOG(WS_LOG_DEBUG,
"Entering wolfSSH_ChannelSendExt(), ID = %d, peerID = %d",
channel->channel, channel->peerChannel);
if (channel->ssh != NULL && SendAfterDisconnect(channel->ssh))
return WS_FATAL_ERROR;
#ifdef DEBUG_WOLFSSH
DumpOctetString(buf, bufSz);
#endif
if (!channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
bytesTxd = WS_CHANNEL_NOT_CONF;
}
else {
WLOG(WS_LOG_DEBUG, "Sending extended data.");
bytesTxd = SendChannelExtendedData(channel->ssh, channel->channel,
(byte*)buf, bufSz);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelSendExt(), bytesTxd = %d",
bytesTxd);
return bytesTxd;
}
int wolfSSH_ChannelExit(WOLFSSH_CHANNEL* channel)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelExit()");
if (channel == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && channel->ssh != NULL &&
SendAfterDisconnect(channel->ssh))
ret = WS_FATAL_ERROR;
/* Both sends address the peer id, 0 until the open is confirmed, so
* this would tear down whichever channel holds 0. */
if (ret == WS_SUCCESS && !channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
ret = WS_CHANNEL_NOT_CONF;
}
if (ret == WS_SUCCESS)
ret = SendChannelEof(channel->ssh, channel->peerChannel);
if (ret == WS_SUCCESS)
ret = SendChannelClose(channel->ssh, channel->peerChannel);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelExit(), ret = %d", ret);
return ret;
}
int wolfSSH_ChannelSendEof(WOLFSSH_CHANNEL* channel)
{
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelSendEof()");
/* Every gate below reaches through ssh, so take it up front. */
if (channel == NULL || channel->ssh == NULL)
ret = WS_BAD_ARGUMENT;
if (ret == WS_SUCCESS && SendAfterDisconnect(channel->ssh))
ret = WS_FATAL_ERROR;
/* Only KEX traffic may go out mid-rekey, RFC 4253 section 7.1. */
if (ret == WS_SUCCESS && channel->ssh->isKeying) {
channel->ssh->error = WS_REKEYING;
ret = WS_REKEYING;
}
/* peerChannel is 0 until the open is confirmed, and SendChannelEof()
* resolves by peer id. */
if (ret == WS_SUCCESS && !channel->openConfirmed) {
WLOG(WS_LOG_DEBUG, "Channel not confirmed yet.");
ret = WS_CHANNEL_NOT_CONF;
}
if (ret == WS_SUCCESS)
ret = SendChannelEof(channel->ssh, channel->peerChannel);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelSendEof(), ret = %d", ret);
return ret;
}
WOLFSSH_CHANNEL* wolfSSH_ChannelNext(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel)
{
WOLFSSH_CHANNEL* nextChannel = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelNext()");
if (ssh != NULL && channel == NULL)
nextChannel = ssh->channelList;
else if (channel != NULL)
nextChannel = channel->next;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelNext(), %s",
nextChannel == NULL ? "none" : "NEXT!");
return nextChannel;
}
int wolfSSH_ChannelGetEof(WOLFSSH_CHANNEL* channel)
{
int eof = 1;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetEof()");
if (channel)
eof = (int)channel->eofRxd;
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelGetEof(), %s",
eof ? "true" : "false");
return eof;
}
static const char* HashNameForId(byte id)
{
enum wc_HashType hash = HashForId(id);
if (hash == WC_HASH_TYPE_SHA)
return "SHA-1";
if (hash == WC_HASH_TYPE_SHA256)
return "SHA-256";
if (hash == WC_HASH_TYPE_SHA384)
return "SHA-384";
if (hash == WC_HASH_TYPE_SHA512)
return "SHA-512";
return "";
}
static const char* CurveNameForId(byte id)
{
#if !defined(WOLFSSH_NO_ECDSA) || !defined(WOLFSSH_NO_ECDH)
switch (wcPrimeForId(id)) {
case ECC_SECP256R1:
return "nistp256";
case ECC_SECP384R1:
return "nistp384";
case ECC_SECP521R1:
return "nistp521";
#ifdef HAVE_CURVE25519
case ECC_X25519:
return "Curve25519";
#endif
}
#endif
return "";
}
static const char* CipherNameForId(byte id)
{
switch (id) {
case ID_AES128_CBC:
return "AES-128 CBC";
case ID_AES192_CBC:
return "AES-192 CBC";
case ID_AES256_CBC:
return "AES-256 CBC";
case ID_AES128_CTR:
return "AES-128 SDCTR";
case ID_AES192_CTR:
return "AES-192 SDCTR";
case ID_AES256_CTR:
return "AES-256 SDCTR";
case ID_AES128_GCM:
return "AES-128 GCM";
case ID_AES192_GCM:
return "AES-192 GCM";
case ID_AES256_GCM:
return "AES-256 GCM";
}
return "";
}
static const char* MacNameForId(byte macid, byte cipherid)
{
if (macid != ID_NONE) {
switch (macid) {
case ID_HMAC_SHA1:
return "HMAC-SHA-1";
case ID_HMAC_SHA1_96:
return "HMAC-SHA-1-96";
case ID_HMAC_SHA2_256:
return "HMAC-SHA-256";
case ID_HMAC_SHA2_512:
return "HMAC-SHA-512";
}
}
else {
switch (cipherid) {
case ID_AES128_GCM:
return "AES128 GCM (in ETM mode)";
case ID_AES192_GCM:
return "AES192 GCM (in ETM mode)";
case ID_AES256_GCM:
return "AES256 GCM (in ETM mode)";
}
}
return "";
}
size_t wolfSSH_GetText(WOLFSSH *ssh, WS_Text id, char *str, size_t strSz)
{
int ret = 0;
#ifndef WOLFSSH_NO_DH
static const char standard_dh_format[] =
"%d-bit Diffie-Hellman with standard group %d";
#endif
if (!ssh || str == NULL || strSz <= 0)
return 0;
switch (id) {
case WOLFSSH_TEXT_KEX_HASH:
ret = WSNPRINTF(str, strSz, "%s", HashNameForId(ssh->kexId));
break;
case WOLFSSH_TEXT_KEX_CURVE:
ret = WSNPRINTF(str, strSz, "%s", CurveNameForId(ssh->kexId));
break;
case WOLFSSH_TEXT_CRYPTO_IN_CIPHER:
ret = WSNPRINTF(str, strSz, "%s",
CipherNameForId(ssh->peerEncryptId));
break;
case WOLFSSH_TEXT_CRYPTO_OUT_CIPHER:
ret = WSNPRINTF(str, strSz, "%s", CipherNameForId(ssh->encryptId));
break;
case WOLFSSH_TEXT_CRYPTO_IN_MAC:
ret = WSNPRINTF(str, strSz, "%s", MacNameForId(ssh->peerMacId,
ssh->peerEncryptId));
break;
case WOLFSSH_TEXT_CRYPTO_OUT_MAC:
ret = WSNPRINTF(str, strSz, "%s", MacNameForId(ssh->macId,
ssh->encryptId));
break;
case WOLFSSH_TEXT_KEX_ALGO:
switch (ssh->kexId) {
case ID_ECDH_SHA2_NISTP256:
case ID_ECDH_SHA2_NISTP384:
case ID_ECDH_SHA2_NISTP521:
#ifndef WOLFSSH_NO_CURVE25519_SHA256
case ID_CURVE25519_SHA256:
case ID_CURVE25519_SHA256_LIBSSH:
#endif
ret = WSNPRINTF(str, strSz, "%s", "ECDH");
break;
#ifndef WOLFSSH_NO_NISTP256_MLKEM768_SHA256
case ID_NISTP256_MLKEM768_SHA256:
ret = WSNPRINTF(str, strSz, "%s",
"ECDH-NISTP256-MLKEM768");
break;
#endif
#ifndef WOLFSSH_NO_NISTP384_MLKEM1024_SHA384
case ID_NISTP384_MLKEM1024_SHA384:
ret = WSNPRINTF(str, strSz, "%s",
"ECDH-NISTP384-MLKEM1024");
break;
#endif
#ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256
case ID_CURVE25519_MLKEM768_SHA256:
ret = WSNPRINTF(str, strSz, "%s",
"ECDH-CURVE25519-MLKEM768");
break;
#endif
#ifndef WOLFSSH_NO_DH
case ID_DH_GROUP1_SHA1:
ret = WSNPRINTF(str, strSz, standard_dh_format,
ssh->primeGroupSz*8, 1);
break;
case ID_DH_GROUP14_SHA1:
case ID_DH_GROUP14_SHA256:
ret = WSNPRINTF(str, strSz, standard_dh_format,
ssh->primeGroupSz*8, 14);
break;
case ID_DH_GROUP16_SHA512:
ret = WSNPRINTF(str, strSz, standard_dh_format,
ssh->primeGroupSz*8, 16);
break;
case ID_DH_GEX_SHA256:
ret = WSNPRINTF(str, strSz,
"%d-bit Diffie-Hellman with server-supplied group",
ssh->primeGroupSz*8);
break;
#endif /* !WOLFSSH_NO_DH */
case ID_EXTINFO_S:
#if defined(__CCRX__)
ret = WSNPRINTF0(str, strSz, "Server extensions KEX");
#else
ret = WSNPRINTF(str, strSz, "Server extensions KEX");
#endif
break;
case ID_EXTINFO_C:
#if defined(__CCRX__)
ret = WSNPRINTF0(str, strSz, "Client extensions KEX");
#else
ret = WSNPRINTF(str, strSz, "Client extensions KEX");
#endif
break;
}
break;
}
return ret < 0 ? 0 : (size_t)ret;
}
void wolfSSH_SetKeyingCompletionCb(WOLFSSH_CTX* ctx, WS_CallbackKeyingCompletion cb)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetKeyingCompletionCb()");
if (ctx)
ctx->keyingCompletionCb = cb;
}
void wolfSSH_SetKeyingCompletionCbCtx(WOLFSSH* ssh, void* ctx)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetKeyingCompletionCbCtx()");
if (ssh)
ssh->keyingCompletionCtx = ctx;
}
const char* wolfSSH_ChannelGetType(const WOLFSSH_CHANNEL* channel)
{
const char* name = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetType()");
if (channel != NULL) {
name = IdToName(channel->channelType);
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelGetType(), name = %s",
channel ? name : "null channel");
return name;
}
WS_SessionType wolfSSH_ChannelGetSessionType(const WOLFSSH_CHANNEL* channel)
{
WS_SessionType type = WOLFSSH_SESSION_UNKNOWN;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetSessionType()");
if (channel) {
type = (WS_SessionType)channel->sessionType;
}
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_ChannelGetSessionType(), type = %d",
type);
return type;
}
#if defined(WOLFSSH_TERM)
/* returns 1 if a PTY was requested, 0 if not, and negative on failure */
int wolfSSH_ChannelIsPty(const WOLFSSH_CHANNEL* channel)
{
if (channel == NULL) {
return WS_BAD_ARGUMENT;
}
return channel->ptyReq;
}
#endif
const char* wolfSSH_ChannelGetSessionCommand(const WOLFSSH_CHANNEL* channel)
{
const char* cmd = NULL;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetSessionCommand()");
if (channel) {
cmd = channel->command;
}
return cmd;
}
word32 wolfSSH_ChannelGetSessionCommandSz(const WOLFSSH_CHANNEL* channel)
{
word32 commandSz = 0;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelGetSessionCommandSz()");
if (channel) {
commandSz = channel->commandSz;
}
return commandSz;
}
/* returns 1 if a session was granted on the channel, 0 if not, and
* negative on failure */
int wolfSSH_ChannelGetSessionGranted(const WOLFSSH_CHANNEL* channel)
{
if (channel == NULL) {
return WS_BAD_ARGUMENT;
}
return channel->sessionGranted;
}
int wolfSSH_CTX_SetChannelOpenCb(WOLFSSH_CTX* ctx, WS_CallbackChannelOpen cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelOpenCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetChannelOpenRespCb(WOLFSSH_CTX* ctx,
WS_CallbackChannelOpen confCb, WS_CallbackChannelOpen failCb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelOpenConfCb = confCb;
ctx->channelOpenFailCb = failCb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetChannelReqShellCb(WOLFSSH_CTX* ctx,
WS_CallbackChannelReq cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelReqShellCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetChannelReqExecCb(WOLFSSH_CTX* ctx,
WS_CallbackChannelReq cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelReqExecCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetChannelReqSubsysCb(WOLFSSH_CTX* ctx,
WS_CallbackChannelReq cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelReqSubsysCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetChannelReqAnyCb(WOLFSSH_CTX* ctx,
WS_CallbackChannelReqAny cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelReqAnyCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_CTX_SetAppChannels(WOLFSSH_CTX* ctx, byte enable)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->appChannels = (enable != 0);
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_SetAppChannels(WOLFSSH* ssh, byte enable)
{
int ret = WS_SSH_NULL_E;
if (ssh != NULL) {
ssh->appChannels = (enable != 0);
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_SetChannelOpenCtx(WOLFSSH* ssh, void* ctx)
{
int ret = WS_SSH_NULL_E;
if (ssh != NULL) {
ssh->channelOpenCtx = ctx;
ret = WS_SUCCESS;
}
return ret;
}
void* wolfSSH_GetChannelOpenCtx(WOLFSSH* ssh)
{
void* ctx = NULL;
if (ssh != NULL) {
ctx = ssh->channelOpenCtx;
}
return ctx;
}
int wolfSSH_SetChannelReqCtx(WOLFSSH* ssh, void* ctx)
{
int ret = WS_SSH_NULL_E;
if (ssh != NULL) {
ssh->channelReqCtx = ctx;
ret = WS_SUCCESS;
}
return ret;
}
void* wolfSSH_GetChannelReqCtx(WOLFSSH* ssh)
{
void* ctx = NULL;
if (ssh != NULL) {
ctx = ssh->channelReqCtx;
}
return ctx;
}
int wolfSSH_CTX_SetChannelEofCb(WOLFSSH_CTX* ctx, WS_CallbackChannelEof cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelEofCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_SetChannelEofCtx(WOLFSSH* ssh, void* ctx)
{
int ret = WS_SSH_NULL_E;
if (ssh != NULL) {
ssh->channelEofCtx = ctx;
ret = WS_SUCCESS;
}
return ret;
}
void* wolfSSH_GetChannelEofCtx(WOLFSSH* ssh)
{
void* ctx = NULL;
if (ssh != NULL) {
ctx = ssh->channelEofCtx;
}
return ctx;
}
int wolfSSH_CTX_SetChannelCloseCb(WOLFSSH_CTX* ctx, WS_CallbackChannelClose cb)
{
int ret = WS_SSH_CTX_NULL_E;
if (ctx != NULL) {
ctx->channelCloseCb = cb;
ret = WS_SUCCESS;
}
return ret;
}
int wolfSSH_SetChannelCloseCtx(WOLFSSH* ssh, void* ctx)
{
int ret = WS_SSH_NULL_E;
if (ssh != NULL) {
ssh->channelCloseCtx = ctx;
ret = WS_SUCCESS;
}
return ret;
}
void* wolfSSH_GetChannelCloseCtx(WOLFSSH* ssh)
{
void* ctx = NULL;
if (ssh != NULL) {
ctx = ssh->channelCloseCtx;
}
return ctx;
}
#if (defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)) && \
!defined(NO_WOLFSSH_SERVER)
/*
* Paths starting with a slash are absolute, rooted at "/". Any path that
* doesn't have a starting slash is assumed to be relative to the default
* path. If the path is empty, return the default path.
*
* The path "/." is stripped out. The path "/.." strips out the previous
* path value. The root path, "/", is always present.
*
* Trailing delimiters are stripped, i.e /tmp/path/ becomes /tmp/path
*
* Example: "/home/fred/frob/frizz/../../../barney/bar/baz/./././../.."
* will return "/home/barney". "/../.." will return "/". "." will return
* the default path.
*
* Note, this function does not care about OS and filesystem issues. The
* SFTP protocol describes how paths are handled in SFTP. Specialized
* behaviors are handled when actually calling the OS functions. Paths
* are further massaged there. For example, the C: drive is treated as
* the path "/C:", and is a directory like any other.
*
* @param defaultPath RealPath of the default directory, usually user's
* @param in requested new path
* @param out output of real path cleanup
* @param outSz size in bytes of buffer 'out'
* @return WS_SUCCESS, WS_BAD_ARGUMENT, or WS_INVALID_PATH_E
*/
int wolfSSH_RealPath(const char* defaultPath, char* in,
char* out, word32 outSz)
{
char* tail = NULL;
char* seg;
word32 inSz, segSz, curSz;
if (in == NULL || out == NULL || outSz == 0) {
return WS_BAD_ARGUMENT;
}
WMEMSET(out, 0, outSz);
inSz = (word32)WSTRLEN(in);
out[0] = '/';
curSz = 1;
if (inSz == 0 || (!WOLFSSH_SFTP_IS_DELIM(in[0]) &&
!WOLFSSH_SFTP_IS_WINPATH(inSz, in))) {
if (defaultPath != NULL) {
curSz = (word32)WSTRLEN(defaultPath);
if (curSz >= outSz) {
return WS_INVALID_PATH_E;
}
WSTRNCPY(out, defaultPath, outSz);
}
}
out[curSz] = 0;
for (seg = WSTRTOK(in, WOLFSSH_SFTP_DELIM, &tail);
seg;
seg = WSTRTOK(NULL, WOLFSSH_SFTP_DELIM, &tail)) {
segSz = (word32)WSTRLEN(seg);
/* Try to match "." */
if (segSz == 1 && seg[0] == '.') {
/* Do nothing. Keep current directory. */
}
/* Try to match ".." */
else if (segSz == 2 && seg[0] == '.' && seg[1] == '.') {
char* prev = strrchr(out, '/');
if (prev != NULL) {
if (prev != out
#ifdef WOLFSSH_ZEPHYR
/* Zephyr FAT fs path names follow the format of '/RAM:'
* and we want to preserve the '/' after this mount
* point definition too. */
&& prev[-1] != ':'
#endif
) {
prev[0] = 0;
curSz = (word32)WSTRLEN(out);
}
else {
/* preserve the root / */
prev[1] = 0;
curSz = 1;
}
}
}
/* Everything else is copied */
else {
word32 sepSz = (curSz != 1) ? 1 : 0;
/* Need room for the optional separator, the segment, and the
* terminating null. Guard the subtraction against underflow. */
if (segSz >= outSz || curSz + sepSz >= outSz - segSz) {
return WS_INVALID_PATH_E;
}
/* Pass the full buffer size to WSTRNCAT: it measures the current
* contents itself and returns NULL if the append would not fit,
* so a truncated append is reported rather than silently kept. */
if (curSz != 1) {
if (WSTRNCAT(out, "/", outSz) == NULL) {
return WS_INVALID_PATH_E;
}
curSz++;
}
if (WSTRNCAT(out, seg, outSz) == NULL) {
return WS_INVALID_PATH_E;
}
curSz += segSz;
}
}
return WS_SUCCESS;
}
#endif /* WOLFSSH_SFTP || WOLFSSH_SCP */
#ifdef WOLFSSH_SHOW_SIZES
void wolfSSH_ShowSizes(void)
{
fprintf(stderr, "wolfSSH struct sizes:\n");
fprintf(stderr, " sizeof(struct %s) = %u\n", "WOLFSSH_CTX",
(word32)sizeof(struct WOLFSSH_CTX));
fprintf(stderr, " sizeof(struct %s) = %u\n", "WOLFSSH",
(word32)sizeof(struct WOLFSSH));
fprintf(stderr, " sizeof(struct %s) = %u\n", "HandshakeInfo",
(word32)sizeof(struct HandshakeInfo));
fprintf(stderr, " sizeof(struct %s) = %u\n", "WOLFSSH_CHANNEL",
(word32)sizeof(struct WOLFSSH_CHANNEL));
fprintf(stderr, " sizeof(struct %s) = %u\n", "WOLFSSH_BUFFER",
(word32)sizeof(struct WOLFSSH_BUFFER));
#ifdef WOLFSSH_SFTP
wolfSSH_SFTP_ShowSizes();
#endif
}
#endif /* WOLFSSH_SHOW_SIZES */