mirror of https://github.com/wolfSSL/wolfssh.git
8069 lines
243 KiB
C
8069 lines
243 KiB
C
/* internal.c
|
|
*
|
|
* Copyright (C) 2014-2019 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 internal module contains the private data and functions. The public
|
|
* API calls into this module to do the work of processing the connections.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <wolfssh/ssh.h>
|
|
#include <wolfssh/internal.h>
|
|
#include <wolfssh/log.h>
|
|
#include <wolfssl/wolfcrypt/asn.h>
|
|
#include <wolfssl/wolfcrypt/dh.h>
|
|
#include <wolfssl/wolfcrypt/rsa.h>
|
|
#include <wolfssl/wolfcrypt/ecc.h>
|
|
#include <wolfssl/wolfcrypt/hmac.h>
|
|
#include <wolfssl/wolfcrypt/integer.h>
|
|
#include <wolfssl/wolfcrypt/signature.h>
|
|
|
|
#ifdef NO_INLINE
|
|
#include <wolfssh/misc.h>
|
|
#else
|
|
#define WOLFSSH_MISC_INCLUDED
|
|
#include "src/misc.c"
|
|
#endif
|
|
|
|
|
|
static const char sshProtoIdStr[] = "SSH-2.0-wolfSSHv"
|
|
LIBWOLFSSH_VERSION_STRING
|
|
"\r\n";
|
|
static const char OpenSSH[] = "SSH-2.0-OpenSSH";
|
|
#ifndef WOLFSSH_DEFAULT_GEXDH_MIN
|
|
#define WOLFSSH_DEFAULT_GEXDH_MIN 1024
|
|
#endif
|
|
#ifndef WOLFSSH_DEFAULT_GEXDH_PREFERRED
|
|
#define WOLFSSH_DEFAULT_GEXDH_PREFERRED 3072
|
|
#endif
|
|
#ifndef WOLFSSH_DEFAULT_GEXDH_MAX
|
|
#define WOLFSSH_DEFAULT_GEXDH_MAX 8192
|
|
#endif
|
|
|
|
|
|
const char* GetErrorString(int err)
|
|
{
|
|
(void)err;
|
|
|
|
#ifdef NO_WOLFSSH_STRINGS
|
|
return "No wolfSSH strings available";
|
|
#else
|
|
switch (err) {
|
|
case WS_FATAL_ERROR:
|
|
return "general function failure";
|
|
|
|
case WS_BAD_ARGUMENT:
|
|
return "bad function argument";
|
|
|
|
case WS_MEMORY_E:
|
|
return "memory allocation failure";
|
|
|
|
case WS_BUFFER_E:
|
|
return "input/output buffer size error";
|
|
|
|
case WS_PARSE_E:
|
|
return "general parsing error";
|
|
|
|
case WS_NOT_COMPILED:
|
|
return "feature not compiled in";
|
|
|
|
case WS_OVERFLOW_E:
|
|
return "would overflow if continued failure";
|
|
|
|
case WS_BAD_USAGE:
|
|
return "bad example usage";
|
|
|
|
case WS_SOCKET_ERROR_E:
|
|
return "socket error";
|
|
|
|
case WS_WANT_READ:
|
|
return "I/O callback would read block error";
|
|
|
|
case WS_WANT_WRITE:
|
|
return "I/O callback would write block error";
|
|
|
|
case WS_RECV_OVERFLOW_E:
|
|
return "receive buffer overflow";
|
|
|
|
case WS_VERSION_E:
|
|
return "peer version unsupported";
|
|
|
|
case WS_SEND_OOB_READ_E:
|
|
return "attempted to read buffer out of bounds";
|
|
|
|
case WS_INPUT_CASE_E:
|
|
return "bad process input state, programming error";
|
|
|
|
case WS_BAD_FILETYPE_E:
|
|
return "bad filetype";
|
|
|
|
case WS_UNIMPLEMENTED_E:
|
|
return "feature not implemented";
|
|
|
|
case WS_RSA_E:
|
|
return "RSA buffer error";
|
|
|
|
case WS_BAD_FILE_E:
|
|
return "bad file";
|
|
|
|
case WS_INVALID_ALGO_ID:
|
|
return "invalid algorithm id";
|
|
|
|
case WS_DECRYPT_E:
|
|
return "decrypt error";
|
|
|
|
case WS_ENCRYPT_E:
|
|
return "encrypt error";
|
|
|
|
case WS_VERIFY_MAC_E:
|
|
return "verify mac error";
|
|
|
|
case WS_CREATE_MAC_E:
|
|
return "create mac error";
|
|
|
|
case WS_RESOURCE_E:
|
|
return "insufficient resources for new channel";
|
|
|
|
case WS_INVALID_CHANTYPE:
|
|
return "peer requested invalid channel type";
|
|
|
|
case WS_INVALID_CHANID:
|
|
return "peer requested invalid channel id";
|
|
|
|
case WS_INVALID_USERNAME:
|
|
return "invalid user name";
|
|
|
|
case WS_CRYPTO_FAILED:
|
|
return "crypto action failed";
|
|
|
|
case WS_INVALID_STATE_E:
|
|
return "invalid state";
|
|
|
|
case WS_EOF:
|
|
return "end of file";
|
|
|
|
case WS_REKEYING:
|
|
return "rekeying with peer";
|
|
|
|
case WS_INVALID_PRIME_CURVE:
|
|
return "invalid prime curve in ecc";
|
|
|
|
case WS_ECC_E:
|
|
return "ECDSA buffer error";
|
|
|
|
case WS_CHANOPEN_FAILED:
|
|
return "peer returned channel open failure";
|
|
|
|
case WS_CHANNEL_CLOSED:
|
|
return "channel closed";
|
|
|
|
case WS_INVALID_PATH_E:
|
|
return "invalid file or directory path";
|
|
|
|
case WS_SCP_CMD_E:
|
|
return "invalid scp command";
|
|
|
|
case WS_SCP_BAD_MSG_E:
|
|
return "invalid scp message received from peer";
|
|
|
|
case WS_SCP_PATH_LEN_E:
|
|
return "scp path length error";
|
|
|
|
case WS_SCP_TIMESTAMP_E:
|
|
return "scp timestamp message error";
|
|
|
|
case WS_SCP_DIR_STACK_EMPTY_E:
|
|
return "scp directory stack empty";
|
|
|
|
case WS_SCP_CONTINUE:
|
|
return "scp continue operation";
|
|
|
|
case WS_SCP_ABORT:
|
|
return "scp abort operation";
|
|
|
|
case WS_SCP_ENTER_DIR:
|
|
return "scp enter directory operation";
|
|
|
|
case WS_SCP_EXIT_DIR:
|
|
return "scp exit directory operation";
|
|
|
|
case WS_SCP_EXIT_DIR_FINAL:
|
|
return "scp final exit directory operation";
|
|
|
|
case WS_SCP_COMPLETE:
|
|
return "scp operation complete";
|
|
|
|
case WS_SCP_INIT:
|
|
return "scp operation verified";
|
|
|
|
case WS_MATCH_KEX_ALGO_E:
|
|
return "cannot match KEX algo with peer";
|
|
|
|
case WS_MATCH_KEY_ALGO_E:
|
|
return "cannot match key algo with peer";
|
|
|
|
case WS_MATCH_ENC_ALGO_E:
|
|
return "cannot match encrypt algo with peer";
|
|
|
|
case WS_MATCH_MAC_ALGO_E:
|
|
return "cannot match MAC algo with peer";
|
|
|
|
case WS_PERMISSIONS:
|
|
return "file permissions error";
|
|
|
|
case WS_SFTP_COMPLETE:
|
|
return "sftp connection established";
|
|
|
|
case WS_NEXT_ERROR:
|
|
return "Getting next value/state results in error";
|
|
|
|
case WS_CHAN_RXD:
|
|
return "Channel data received";
|
|
|
|
case WS_INVALID_EXTDATA:
|
|
return "invalid extended data type";
|
|
|
|
case WS_CHAN_PENDING:
|
|
return "channel open pending";
|
|
|
|
case WS_SFTP_BAD_REQ_ID:
|
|
return "sftp bad request id";
|
|
|
|
case WS_SFTP_BAD_REQ_TYPE:
|
|
return "sftp bad request response type";
|
|
|
|
case WS_SFTP_STATUS_NOT_OK:
|
|
return "sftp status not OK";
|
|
|
|
case WS_SFTP_FILE_DNE:
|
|
return "sftp file does not exist";
|
|
|
|
case WS_SIZE_ONLY:
|
|
return "Only getting the size of buffer needed";
|
|
|
|
case WS_CLOSE_FILE_E:
|
|
return "Unable to close local file";
|
|
|
|
case WS_PUBKEY_REJECTED_E:
|
|
return "server's public key is rejected";
|
|
|
|
case WS_EXTDATA:
|
|
return "Extended Data available to be read";
|
|
|
|
default:
|
|
return "Unknown error code";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
static int wsHighwater(byte dir, void* ctx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
(void)dir;
|
|
|
|
if (ctx) {
|
|
WOLFSSH* ssh = (WOLFSSH*)ctx;
|
|
|
|
WLOG(WS_LOG_DEBUG, "HIGHWATER MARK: (%u) %s",
|
|
wolfSSH_GetHighwater(ssh),
|
|
(dir == WOLFSSH_HWSIDE_RECEIVE) ? "receive" : "transmit");
|
|
|
|
ret = wolfSSH_TriggerKeyExchange(ssh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE void HighwaterCheck(WOLFSSH* ssh, byte side)
|
|
{
|
|
if (!ssh->highwaterFlag && ssh->highwaterMark &&
|
|
(ssh->txCount >= ssh->highwaterMark ||
|
|
ssh->rxCount >= ssh->highwaterMark)) {
|
|
|
|
WLOG(WS_LOG_DEBUG, "%s over high water mark",
|
|
(side == WOLFSSH_HWSIDE_TRANSMIT) ? "Transmit" : "Receive");
|
|
|
|
ssh->highwaterFlag = 1;
|
|
|
|
if (ssh->ctx->highwaterCb)
|
|
ssh->ctx->highwaterCb(side, ssh->highwaterCtx);
|
|
}
|
|
}
|
|
|
|
|
|
static HandshakeInfo* HandshakeInfoNew(void* heap)
|
|
{
|
|
HandshakeInfo* newHs;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering HandshakeInfoNew()");
|
|
newHs = (HandshakeInfo*)WMALLOC(sizeof(HandshakeInfo),
|
|
heap, DYNTYPE_HS);
|
|
if (newHs != NULL) {
|
|
WMEMSET(newHs, 0, sizeof(HandshakeInfo));
|
|
newHs->kexId = ID_NONE;
|
|
newHs->pubKeyId = ID_NONE;
|
|
newHs->encryptId = ID_NONE;
|
|
newHs->macId = ID_NONE;
|
|
newHs->blockSz = MIN_BLOCK_SZ;
|
|
newHs->hashId = WC_HASH_TYPE_NONE;
|
|
newHs->dhGexMinSz = WOLFSSH_DEFAULT_GEXDH_MIN;
|
|
newHs->dhGexPreferredSz = WOLFSSH_DEFAULT_GEXDH_PREFERRED;
|
|
newHs->dhGexMaxSz = WOLFSSH_DEFAULT_GEXDH_MAX;
|
|
}
|
|
|
|
return newHs;
|
|
}
|
|
|
|
|
|
static void HandshakeInfoFree(HandshakeInfo* hs, void* heap)
|
|
{
|
|
(void)heap;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering HandshakeInfoFree()");
|
|
if (hs) {
|
|
WFREE(hs->kexInit, heap, DYNTYPE_STRING);
|
|
WFREE(hs->primeGroup, heap, DYNTYPE_MPINT);
|
|
WFREE(hs->generator, heap, DYNTYPE_MPINT);
|
|
ForceZero(hs, sizeof(HandshakeInfo));
|
|
WFREE(hs, heap, DYNTYPE_HS);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_WOLFSSH
|
|
|
|
static const char cannedBanner[] =
|
|
"CANNED BANNER\r\n"
|
|
"This server is an example test server. "
|
|
"It should have its own banner, but\r\n"
|
|
"it is currently using a canned one in "
|
|
"the library. Be happy or not.\r\n";
|
|
static const word32 cannedBannerSz = sizeof(cannedBanner) - 1;
|
|
|
|
#endif /* DEBUG_WOLFSSH */
|
|
|
|
|
|
WOLFSSH_CTX* CtxInit(WOLFSSH_CTX* ctx, byte side, void* heap)
|
|
{
|
|
WLOG(WS_LOG_DEBUG, "Entering CtxInit()");
|
|
|
|
if (ctx == NULL)
|
|
return ctx;
|
|
|
|
WMEMSET(ctx, 0, sizeof(WOLFSSH_CTX));
|
|
|
|
if (heap)
|
|
ctx->heap = heap;
|
|
|
|
ctx->side = side;
|
|
#ifndef WOLFSSH_USER_IO
|
|
ctx->ioRecvCb = wsEmbedRecv;
|
|
ctx->ioSendCb = wsEmbedSend;
|
|
#endif /* WOLFSSH_USER_IO */
|
|
ctx->highwaterMark = DEFAULT_HIGHWATER_MARK;
|
|
ctx->highwaterCb = wsHighwater;
|
|
#if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \
|
|
!defined(NO_FILESYSTEM)
|
|
ctx->scpRecvCb = wsScpRecvCallback;
|
|
ctx->scpSendCb = wsScpSendCallback;
|
|
#endif /* WOLFSSH_SCP */
|
|
#ifdef DEBUG_WOLFSSH
|
|
ctx->banner = cannedBanner;
|
|
ctx->bannerSz = cannedBannerSz;
|
|
#endif /* DEBUG_WOLFSSH */
|
|
ctx->windowSz = DEFAULT_WINDOW_SZ;
|
|
ctx->maxPacketSz = DEFAULT_MAX_PACKET_SZ;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
|
|
void CtxResourceFree(WOLFSSH_CTX* ctx)
|
|
{
|
|
WLOG(WS_LOG_DEBUG, "Entering CtxResourceFree()");
|
|
|
|
if (ctx->privateKey) {
|
|
ForceZero(ctx->privateKey, ctx->privateKeySz);
|
|
WFREE(ctx->privateKey, ctx->heap, DYNTYPE_PRIVKEY);
|
|
}
|
|
}
|
|
|
|
|
|
WOLFSSH* SshInit(WOLFSSH* ssh, WOLFSSH_CTX* ctx)
|
|
{
|
|
#if defined(STM32F2) || defined(STM32F4)
|
|
/* avoid name conflict in "stm32fnnnxx.h" */
|
|
#undef RNG
|
|
#define RNG WC_RNG
|
|
#endif
|
|
HandshakeInfo* handshake;
|
|
RNG* rng;
|
|
void* heap;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SshInit()");
|
|
|
|
if (ssh == NULL || ctx == NULL)
|
|
return ssh;
|
|
heap = ctx->heap;
|
|
|
|
handshake = HandshakeInfoNew(heap);
|
|
rng = (RNG*)WMALLOC(sizeof(RNG), heap, DYNTYPE_RNG);
|
|
|
|
if (handshake == NULL || rng == NULL || wc_InitRng(rng) != 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG, "SshInit: Cannot allocate memory.\n");
|
|
WFREE(handshake, heap, DYNTYPE_HS);
|
|
WFREE(rng, heap, DYNTYPE_RNG);
|
|
WFREE(ssh, heap, DYNTYPE_SSH);
|
|
return NULL;
|
|
}
|
|
|
|
WMEMSET(ssh, 0, sizeof(WOLFSSH)); /* default init to zeros */
|
|
|
|
ssh->ctx = ctx;
|
|
ssh->error = WS_SUCCESS;
|
|
#ifdef USE_WINDOWS_API
|
|
ssh->rfd = INVALID_SOCKET;
|
|
ssh->wfd = INVALID_SOCKET;
|
|
#else
|
|
ssh->rfd = -1; /* set to invalid */
|
|
ssh->wfd = -1; /* set to invalid */
|
|
#endif
|
|
ssh->ioReadCtx = &ssh->rfd; /* prevent invalid access if not correctly */
|
|
ssh->ioWriteCtx = &ssh->wfd; /* set */
|
|
ssh->highwaterMark = ctx->highwaterMark;
|
|
ssh->highwaterCtx = (void*)ssh;
|
|
ssh->acceptState = ACCEPT_BEGIN;
|
|
ssh->clientState = CLIENT_BEGIN;
|
|
ssh->isKeying = 1;
|
|
ssh->nextChannel = DEFAULT_NEXT_CHANNEL;
|
|
ssh->blockSz = MIN_BLOCK_SZ;
|
|
ssh->encryptId = ID_NONE;
|
|
ssh->macId = ID_NONE;
|
|
ssh->peerBlockSz = MIN_BLOCK_SZ;
|
|
ssh->rng = rng;
|
|
ssh->kSz = sizeof(ssh->k);
|
|
ssh->handshake = handshake;
|
|
ssh->connectChannelId = WOLFSSH_SESSION_SHELL;
|
|
#ifdef WOLFSSH_SCP
|
|
ssh->scpRequestState = SCP_PARSE_COMMAND;
|
|
ssh->scpConfirmMsg = NULL;
|
|
ssh->scpConfirmMsgSz = 0;
|
|
ssh->scpRecvCtx = NULL;
|
|
#if !defined(WOLFSSH_SCP_USER_CALLBACKS) && !defined(NO_FILESYSTEM)
|
|
ssh->scpSendCtx = &(ssh->scpSendCbCtx);
|
|
#else
|
|
ssh->scpSendCtx = NULL;
|
|
#endif
|
|
ssh->scpFileBuffer = NULL;
|
|
ssh->scpFileBufferSz = 0;
|
|
ssh->scpFileName = NULL;
|
|
ssh->scpFileNameSz = 0;
|
|
ssh->scpTimestamp = 0;
|
|
ssh->scpATime = 0;
|
|
ssh->scpMTime = 0;
|
|
ssh->scpRequestType = WOLFSSH_SCP_SINGLE_FILE_REQUEST;
|
|
ssh->scpIsRecursive = 0;
|
|
ssh->scpDirection = WOLFSSH_SCP_DIR_NONE;
|
|
#endif
|
|
|
|
#ifdef WOLFSSH_SFTP
|
|
ssh->sftpState = SFTP_BEGIN;
|
|
#endif
|
|
|
|
if (BufferInit(&ssh->inputBuffer, 0, ctx->heap) != WS_SUCCESS ||
|
|
BufferInit(&ssh->outputBuffer, 0, ctx->heap) != WS_SUCCESS ||
|
|
BufferInit(&ssh->extDataBuffer, 0, ctx->heap) != WS_SUCCESS) {
|
|
|
|
wolfSSH_free(ssh);
|
|
ssh = NULL;
|
|
}
|
|
|
|
return ssh;
|
|
}
|
|
|
|
|
|
void SshResourceFree(WOLFSSH* ssh, void* heap)
|
|
{
|
|
/* when ssh holds resources, free here */
|
|
(void)heap;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering sshResourceFree()");
|
|
|
|
ShrinkBuffer(&ssh->inputBuffer, 1);
|
|
ShrinkBuffer(&ssh->outputBuffer, 1);
|
|
ShrinkBuffer(&ssh->extDataBuffer, 1);
|
|
ForceZero(ssh->k, ssh->kSz);
|
|
HandshakeInfoFree(ssh->handshake, heap);
|
|
ForceZero(&ssh->keys, sizeof(Keys));
|
|
ForceZero(&ssh->peerKeys, sizeof(Keys));
|
|
if (ssh->rng) {
|
|
wc_FreeRng(ssh->rng);
|
|
WFREE(ssh->rng, heap, DYNTYPE_RNG);
|
|
}
|
|
if (ssh->userName) {
|
|
WFREE(ssh->userName, heap, DYNTYPE_STRING);
|
|
}
|
|
if (ssh->peerProtoId) {
|
|
WFREE(ssh->peerProtoId, heap, DYNTYPE_STRING);
|
|
}
|
|
if (ssh->channelList) {
|
|
WOLFSSH_CHANNEL* cur = ssh->channelList;
|
|
WOLFSSH_CHANNEL* next;
|
|
while (cur) {
|
|
next = cur->next;
|
|
ChannelDelete(cur, heap);
|
|
cur = next;
|
|
}
|
|
}
|
|
#ifdef WOLFSSH_SCP
|
|
if (ssh->scpConfirmMsg) {
|
|
WFREE(ssh->scpConfirmMsg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpConfirmMsg = NULL;
|
|
ssh->scpConfirmMsgSz = 0;
|
|
}
|
|
if (ssh->scpFileBuffer) {
|
|
ForceZero(ssh->scpFileBuffer, ssh->scpFileBufferSz);
|
|
WFREE(ssh->scpFileBuffer, ssh->ctx->heap, DYNTYPE_BUFFER);
|
|
ssh->scpFileBuffer = NULL;
|
|
ssh->scpFileBufferSz = 0;
|
|
}
|
|
if (ssh->scpFileName) {
|
|
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpFileName = NULL;
|
|
ssh->scpFileNameSz = 0;
|
|
}
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
WFREE(ssh->scpBasePathDynamic, ssh->ctx->heap, DYNTYPE_BUFFER);
|
|
ssh->scpBasePathDynamic = NULL;
|
|
ssh->scpBasePathSz = 0;
|
|
#endif
|
|
#endif
|
|
#ifdef WOLFSSH_SFTP
|
|
if (ssh->sftpDefaultPath) {
|
|
WFREE(ssh->sftpDefaultPath, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->sftpDefaultPath = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
|
|
const byte* in, word32 inSz,
|
|
int format, int type)
|
|
{
|
|
int dynamicType;
|
|
void* heap;
|
|
byte* der;
|
|
word32 derSz;
|
|
|
|
if (ctx == NULL || in == NULL || inSz == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (format != WOLFSSH_FORMAT_ASN1 && format != WOLFSSH_FORMAT_PEM &&
|
|
format != WOLFSSH_FORMAT_RAW)
|
|
return WS_BAD_FILETYPE_E;
|
|
|
|
if (type == BUFTYPE_CA)
|
|
dynamicType = DYNTYPE_CA;
|
|
else if (type == BUFTYPE_CERT)
|
|
dynamicType = DYNTYPE_CERT;
|
|
else if (type == BUFTYPE_PRIVKEY)
|
|
dynamicType = DYNTYPE_PRIVKEY;
|
|
else
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
heap = ctx->heap;
|
|
|
|
if (format == WOLFSSH_FORMAT_PEM)
|
|
return WS_UNIMPLEMENTED_E;
|
|
else {
|
|
/* format is ASN1 or RAW */
|
|
der = (byte*)WMALLOC(inSz, heap, dynamicType);
|
|
if (der == NULL)
|
|
return WS_MEMORY_E;
|
|
WMEMCPY(der, in, inSz);
|
|
derSz = inSz;
|
|
}
|
|
|
|
/* Maybe decrypt */
|
|
|
|
if (type == BUFTYPE_PRIVKEY) {
|
|
if (ctx->privateKey)
|
|
WFREE(ctx->privateKey, heap, dynamicType);
|
|
ctx->privateKey = der;
|
|
ctx->privateKeySz = derSz;
|
|
}
|
|
else {
|
|
WFREE(der, heap, dynamicType);
|
|
return WS_UNIMPLEMENTED_E;
|
|
}
|
|
|
|
if (type == BUFTYPE_PRIVKEY && format != WOLFSSH_FORMAT_RAW) {
|
|
/* Check RSA key */
|
|
union {
|
|
RsaKey rsa;
|
|
ecc_key ecc;
|
|
} key;
|
|
word32 scratch = 0;
|
|
int ret;
|
|
|
|
if (wc_InitRsaKey(&key.rsa, NULL) < 0)
|
|
return WS_RSA_E;
|
|
|
|
ret = wc_RsaPrivateKeyDecode(der, &scratch, &key.rsa, derSz);
|
|
wc_FreeRsaKey(&key.rsa);
|
|
|
|
if (ret < 0) {
|
|
/* Couldn't decode as RSA key. Try decoding as ECC key. */
|
|
scratch = 0;
|
|
if (wc_ecc_init_ex(&key.ecc, ctx->heap, INVALID_DEVID) != 0)
|
|
return WS_ECC_E;
|
|
|
|
ret = wc_EccPrivateKeyDecode(ctx->privateKey, &scratch,
|
|
&key.ecc, ctx->privateKeySz);
|
|
if (ret == 0) {
|
|
int curveId = wc_ecc_get_curve_id(key.ecc.idx);
|
|
if (curveId == ECC_SECP256R1 ||
|
|
curveId == ECC_SECP384R1 ||
|
|
curveId == ECC_SECP521R1) {
|
|
|
|
ctx->useEcc = curveId;
|
|
}
|
|
else
|
|
ret = WS_BAD_FILE_E;
|
|
}
|
|
wc_ecc_free(&key.ecc);
|
|
|
|
if (ret != 0)
|
|
return WS_BAD_FILE_E;
|
|
}
|
|
}
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
int GenerateKey(byte hashId, byte keyId,
|
|
byte* key, word32 keySz,
|
|
const byte* k, word32 kSz,
|
|
const byte* h, word32 hSz,
|
|
const byte* sessionId, word32 sessionIdSz)
|
|
{
|
|
word32 blocks, remainder;
|
|
wc_HashAlg hash;
|
|
byte kPad = 0;
|
|
byte pad = 0;
|
|
byte kSzFlat[LENGTH_SZ];
|
|
int digestSz;
|
|
int ret;
|
|
|
|
if (key == NULL || keySz == 0 ||
|
|
k == NULL || kSz == 0 ||
|
|
h == NULL || hSz == 0 ||
|
|
sessionId == NULL || sessionIdSz == 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG, "GK: bad argument");
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
digestSz = wc_HashGetDigestSize((enum wc_HashType)hashId);
|
|
if (digestSz == 0) {
|
|
WLOG(WS_LOG_DEBUG, "GK: bad hash ID");
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (k[0] & 0x80) kPad = 1;
|
|
c32toa(kSz + kPad, kSzFlat);
|
|
|
|
blocks = keySz / digestSz;
|
|
remainder = keySz % digestSz;
|
|
|
|
ret = wc_HashInit(&hash, (enum wc_HashType)hashId);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, kSzFlat, LENGTH_SZ);
|
|
if (ret == WS_SUCCESS && kPad)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, &pad, 1);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, k, kSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, h, hSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, &keyId, sizeof(keyId));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, sessionId, sessionIdSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (blocks == 0) {
|
|
if (remainder > 0) {
|
|
byte lastBlock[WC_MAX_DIGEST_SIZE];
|
|
ret = wc_HashFinal(&hash, (enum wc_HashType)hashId, lastBlock);
|
|
if (ret == WS_SUCCESS)
|
|
WMEMCPY(key, lastBlock, remainder);
|
|
}
|
|
}
|
|
else {
|
|
word32 runningKeySz, curBlock;
|
|
|
|
runningKeySz = digestSz;
|
|
ret = wc_HashFinal(&hash, (enum wc_HashType)hashId, key);
|
|
|
|
for (curBlock = 1; curBlock < blocks; curBlock++) {
|
|
ret = wc_HashInit(&hash, (enum wc_HashType)hashId);
|
|
if (ret != WS_SUCCESS) break;
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, kSzFlat, LENGTH_SZ);
|
|
if (ret != WS_SUCCESS) break;
|
|
if (kPad)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, &pad, 1);
|
|
if (ret != WS_SUCCESS) break;
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, k, kSz);
|
|
if (ret != WS_SUCCESS) break;
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, h, hSz);
|
|
if (ret != WS_SUCCESS) break;
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, key, runningKeySz);
|
|
if (ret != WS_SUCCESS) break;
|
|
ret = wc_HashFinal(&hash, (enum wc_HashType)hashId, key + runningKeySz);
|
|
if (ret != WS_SUCCESS) break;
|
|
runningKeySz += digestSz;
|
|
}
|
|
|
|
if (remainder > 0) {
|
|
byte lastBlock[WC_MAX_DIGEST_SIZE];
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashInit(&hash, (enum wc_HashType)hashId);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, kSzFlat, LENGTH_SZ);
|
|
if (ret == WS_SUCCESS && kPad)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, &pad, 1);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, k, kSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, h, hSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, key, runningKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashFinal(&hash, (enum wc_HashType)hashId, lastBlock);
|
|
if (ret == WS_SUCCESS)
|
|
WMEMCPY(key + runningKeySz, lastBlock, remainder);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret != WS_SUCCESS)
|
|
ret = WS_CRYPTO_FAILED;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int GenerateKeys(WOLFSSH* ssh)
|
|
{
|
|
Keys* cK;
|
|
Keys* sK;
|
|
byte hashId;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
else {
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER) {
|
|
cK = &ssh->handshake->peerKeys;
|
|
sK = &ssh->handshake->keys;
|
|
}
|
|
else {
|
|
cK = &ssh->handshake->keys;
|
|
sK = &ssh->handshake->peerKeys;
|
|
}
|
|
hashId = ssh->handshake->hashId;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'A',
|
|
cK->iv, cK->ivSz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'B',
|
|
sK->iv, sK->ivSz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'C',
|
|
cK->encKey, cK->encKeySz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'D',
|
|
sK->encKey, sK->encKeySz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
if (!ssh->handshake->aeadMode) {
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'E',
|
|
cK->macKey, cK->macKeySz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKey(hashId, 'F',
|
|
sK->macKey, sK->macKeySz,
|
|
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
}
|
|
#ifdef SHOW_SECRETS
|
|
if (ret == WS_SUCCESS) {
|
|
printf("\n** Showing Secrets **\nK:\n");
|
|
DumpOctetString(ssh->k, ssh->kSz);
|
|
printf("H:\n");
|
|
DumpOctetString(ssh->h, ssh->hSz);
|
|
printf("Session ID:\n");
|
|
DumpOctetString(ssh->sessionId, ssh->sessionIdSz);
|
|
printf("A:\n");
|
|
DumpOctetString(cK->iv, cK->ivSz);
|
|
printf("B:\n");
|
|
DumpOctetString(sK->iv, sK->ivSz);
|
|
printf("C:\n");
|
|
DumpOctetString(cK->encKey, cK->encKeySz);
|
|
printf("D:\n");
|
|
DumpOctetString(sK->encKey, sK->encKeySz);
|
|
printf("E:\n");
|
|
DumpOctetString(cK->macKey, cK->macKeySz);
|
|
printf("F:\n");
|
|
DumpOctetString(sK->macKey, sK->macKeySz);
|
|
printf("\n");
|
|
}
|
|
#endif /* SHOW_SECRETS */
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
byte id;
|
|
const char* name;
|
|
} NameIdPair;
|
|
|
|
|
|
static const NameIdPair NameIdMap[] = {
|
|
{ ID_NONE, "none" },
|
|
|
|
/* Encryption IDs */
|
|
{ ID_AES128_CBC, "aes128-cbc" },
|
|
{ ID_AES128_CTR, "aes128-ctr" },
|
|
{ ID_AES128_GCM, "aes128-gcm@openssh.com" },
|
|
|
|
/* Integrity IDs */
|
|
{ ID_HMAC_SHA1, "hmac-sha1" },
|
|
{ ID_HMAC_SHA1_96, "hmac-sha1-96" },
|
|
{ ID_HMAC_SHA2_256, "hmac-sha2-256" },
|
|
|
|
/* Key Exchange IDs */
|
|
{ ID_DH_GROUP1_SHA1, "diffie-hellman-group1-sha1" },
|
|
{ ID_DH_GROUP14_SHA1, "diffie-hellman-group14-sha1" },
|
|
{ ID_DH_GEX_SHA256, "diffie-hellman-group-exchange-sha256" },
|
|
{ ID_ECDH_SHA2_NISTP256, "ecdh-sha2-nistp256" },
|
|
{ ID_ECDH_SHA2_NISTP384, "ecdh-sha2-nistp384" },
|
|
{ ID_ECDH_SHA2_NISTP521, "ecdh-sha2-nistp521" },
|
|
|
|
/* Public Key IDs */
|
|
{ ID_SSH_RSA, "ssh-rsa" },
|
|
{ ID_ECDSA_SHA2_NISTP256, "ecdsa-sha2-nistp256" },
|
|
{ ID_ECDSA_SHA2_NISTP384, "ecdsa-sha2-nistp384" },
|
|
{ ID_ECDSA_SHA2_NISTP521, "ecdsa-sha2-nistp521" },
|
|
|
|
/* Service IDs */
|
|
{ ID_SERVICE_USERAUTH, "ssh-userauth" },
|
|
{ ID_SERVICE_CONNECTION, "ssh-connection" },
|
|
|
|
/* UserAuth IDs */
|
|
{ ID_USERAUTH_PASSWORD, "password" },
|
|
{ ID_USERAUTH_PUBLICKEY, "publickey" },
|
|
|
|
/* Channel Type IDs */
|
|
{ ID_CHANTYPE_SESSION, "session" },
|
|
#ifdef WOLFSSH_FWD
|
|
{ ID_CHANTYPE_TCPIP_FORWARD, "forwarded-tcpip" },
|
|
{ ID_CHANTYPE_TCPIP_DIRECT, "direct-tcpip" },
|
|
#endif /* WOLFSSH_FWD */
|
|
};
|
|
|
|
|
|
byte NameToId(const char* name, word32 nameSz)
|
|
{
|
|
byte id = ID_UNKNOWN;
|
|
word32 i;
|
|
|
|
for (i = 0; i < (sizeof(NameIdMap)/sizeof(NameIdPair)); i++) {
|
|
if (nameSz == WSTRLEN(NameIdMap[i].name) &&
|
|
WSTRNCMP(name, NameIdMap[i].name, nameSz) == 0) {
|
|
|
|
id = NameIdMap[i].id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
const char* IdToName(byte id)
|
|
{
|
|
const char* name = "unknown";
|
|
word32 i;
|
|
|
|
for (i = 0; i < (sizeof(NameIdMap)/sizeof(NameIdPair)); i++) {
|
|
if (NameIdMap[i].id == id) {
|
|
name = NameIdMap[i].name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
WOLFSSH_CHANNEL* ChannelNew(WOLFSSH* ssh, byte channelType,
|
|
word32 initialWindowSz, word32 maxPacketSz)
|
|
{
|
|
WOLFSSH_CHANNEL* newChannel = NULL;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering ChannelNew()");
|
|
if (ssh == NULL || ssh->ctx == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Trying to create new channel without ssh or ctx");
|
|
}
|
|
else {
|
|
void* heap = ssh->ctx->heap;
|
|
|
|
newChannel = (WOLFSSH_CHANNEL*)WMALLOC(sizeof(WOLFSSH_CHANNEL),
|
|
heap, DYNTYPE_CHANNEL);
|
|
if (newChannel != NULL)
|
|
{
|
|
byte* buffer;
|
|
|
|
buffer = (byte*)WMALLOC(initialWindowSz, heap, DYNTYPE_BUFFER);
|
|
if (buffer != NULL) {
|
|
WMEMSET(newChannel, 0, sizeof(WOLFSSH_CHANNEL));
|
|
newChannel->ssh = ssh;
|
|
newChannel->channelType = channelType;
|
|
newChannel->channel = ssh->nextChannel++;
|
|
newChannel->windowSz = initialWindowSz;
|
|
newChannel->maxPacketSz = maxPacketSz;
|
|
/*
|
|
* In the context of the channel input buffer, the buffer is
|
|
* a fixed size. The property length will be the insert point
|
|
* for new received data. The property idx will be the pull
|
|
* point for the data.
|
|
*/
|
|
newChannel->inputBuffer.heap = heap;
|
|
newChannel->inputBuffer.buffer = buffer;
|
|
newChannel->inputBuffer.bufferSz = initialWindowSz;
|
|
newChannel->inputBuffer.dynamicFlag = 1;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "Unable to allocate new channel's buffer");
|
|
WFREE(newChannel, heap, DYNTYPE_CHANNEL);
|
|
newChannel = NULL;
|
|
}
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "Unable to allocate new channel");
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_INFO, "Leaving ChannelNew(), ret = %p", newChannel);
|
|
|
|
return newChannel;
|
|
}
|
|
|
|
|
|
void ChannelDelete(WOLFSSH_CHANNEL* channel, void* heap)
|
|
{
|
|
(void)heap;
|
|
|
|
if (channel) {
|
|
#ifdef WOLFSSH_FWD
|
|
if (channel->host)
|
|
WFREE(channel->host, heap, DYNTYPE_STRING);
|
|
if (channel->origin)
|
|
WFREE(channel->origin, heap, DYNTYPE_STRING);
|
|
#endif /* WOLFSSH_FWD */
|
|
WFREE(channel->inputBuffer.buffer,
|
|
channel->inputBuffer.heap, DYNTYPE_BUFFER);
|
|
if (channel->command)
|
|
WFREE(channel->command, heap, DYNTYPE_STRING);
|
|
WFREE(channel, heap, DYNTYPE_CHANNEL);
|
|
}
|
|
}
|
|
|
|
|
|
WOLFSSH_CHANNEL* ChannelFind(WOLFSSH* ssh, word32 channel, byte peer)
|
|
{
|
|
WOLFSSH_CHANNEL* findChannel = NULL;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering ChannelFind(): %s %u",
|
|
peer ? "peer" : "self", channel);
|
|
|
|
if (ssh == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Null ssh, not looking for channel");
|
|
}
|
|
else {
|
|
WOLFSSH_CHANNEL* list = ssh->channelList;
|
|
word32 listSz = ssh->channelListSz;
|
|
|
|
while (list && listSz) {
|
|
if (channel == ((peer == WS_CHANNEL_ID_PEER) ?
|
|
list->peerChannel : list->channel)) {
|
|
findChannel = list;
|
|
break;
|
|
}
|
|
list = list->next;
|
|
listSz--;
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving ChannelFind(): %p", findChannel);
|
|
|
|
return findChannel;
|
|
}
|
|
|
|
|
|
int ChannelUpdatePeer(WOLFSSH_CHANNEL* channel, word32 peerChannelId,
|
|
word32 peerInitialWindowSz, word32 peerMaxPacketSz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (channel == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
else {
|
|
channel->peerChannel = peerChannelId;
|
|
channel->peerWindowSz = peerInitialWindowSz;
|
|
channel->peerMaxPacketSz = peerMaxPacketSz;
|
|
channel->openConfirmed = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_FWD
|
|
int ChannelUpdateForward(WOLFSSH_CHANNEL* channel,
|
|
const char* host, word32 hostPort,
|
|
const char* origin, word32 originPort)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
char* hostCopy = NULL;
|
|
char* originCopy = NULL;
|
|
word32 hostSz;
|
|
word32 originSz;
|
|
|
|
if (channel == NULL || host == NULL || origin == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
else {
|
|
void* heap = channel->ssh->ctx->heap;
|
|
|
|
hostSz = (word32)WSTRLEN(host) + 1;
|
|
originSz = (word32)WSTRLEN(origin) + 1;
|
|
hostCopy = (char*)WMALLOC(hostSz, heap, DYNTYPE_STRING);
|
|
originCopy = (char*)WMALLOC(originSz, heap, DYNTYPE_STRING);
|
|
if (hostCopy == NULL || originCopy == NULL) {
|
|
WFREE(hostCopy, heap, DYNTYPE_STRING);
|
|
WFREE(originCopy, heap, DYNTYPE_STRING);
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WSTRNCPY(hostCopy, host, hostSz);
|
|
WSTRNCPY(originCopy, origin, originSz);
|
|
channel->host = hostCopy;
|
|
channel->hostPort = hostPort;
|
|
channel->origin = originCopy;
|
|
channel->originPort = originPort;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* WOLFSSH_FWD */
|
|
|
|
|
|
int ChannelAppend(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering ChannelAppend()");
|
|
|
|
if (ssh == NULL || channel == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->channelList == NULL) {
|
|
ssh->channelList = channel;
|
|
ssh->channelListSz = 1;
|
|
}
|
|
else {
|
|
WOLFSSH_CHANNEL* cur = ssh->channelList;
|
|
while (cur->next != NULL)
|
|
cur = cur->next;
|
|
cur->next = channel;
|
|
ssh->channelListSz++;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving ChannelAppend(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int ChannelRemove(WOLFSSH* ssh, word32 channel, byte peer)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* list;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering ChannelRemove()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
list = ssh->channelList;
|
|
if (list == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WOLFSSH_CHANNEL* prev = NULL;
|
|
word32 listSz = ssh->channelListSz;
|
|
|
|
while (list && listSz) {
|
|
if (channel == ((peer == WS_CHANNEL_ID_PEER) ?
|
|
list->peerChannel : list->channel)) {
|
|
if (prev == NULL)
|
|
ssh->channelList = list->next;
|
|
else
|
|
prev->next = list->next;
|
|
ChannelDelete(list, ssh->ctx->heap);
|
|
ssh->channelListSz--;
|
|
|
|
break;
|
|
}
|
|
prev = list;
|
|
list = list->next;
|
|
listSz--;
|
|
}
|
|
|
|
if (listSz == 0)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving ChannelRemove(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int ChannelPutData(WOLFSSH_CHANNEL* channel, byte* data, word32 dataSz)
|
|
{
|
|
Buffer* inBuf;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering ChannelPutData()");
|
|
|
|
if (channel == NULL || data == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
inBuf = &channel->inputBuffer;
|
|
|
|
if (inBuf->length < inBuf->bufferSz &&
|
|
inBuf->length + dataSz <= inBuf->bufferSz) {
|
|
|
|
WMEMCPY(inBuf->buffer + inBuf->length, data, dataSz);
|
|
inBuf->length += dataSz;
|
|
|
|
WLOG(WS_LOG_INFO, " dataSz = %u", dataSz);
|
|
WLOG(WS_LOG_INFO, " windowSz = %u", channel->windowSz);
|
|
channel->windowSz -= dataSz;
|
|
WLOG(WS_LOG_INFO, " windowSz = %u", channel->windowSz);
|
|
}
|
|
else {
|
|
return WS_RECV_OVERFLOW_E;
|
|
}
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
int BufferInit(Buffer* buffer, word32 size, void* heap)
|
|
{
|
|
if (buffer == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (size <= STATIC_BUFFER_LEN)
|
|
size = STATIC_BUFFER_LEN;
|
|
|
|
WMEMSET(buffer, 0, sizeof(Buffer));
|
|
buffer->heap = heap;
|
|
buffer->bufferSz = size;
|
|
if (size > STATIC_BUFFER_LEN) {
|
|
buffer->buffer = (byte*)WMALLOC(size, heap, DYNTYPE_BUFFER);
|
|
if (buffer->buffer == NULL)
|
|
return WS_MEMORY_E;
|
|
buffer->dynamicFlag = 1;
|
|
}
|
|
else
|
|
buffer->buffer = buffer->staticBuffer;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
int GrowBuffer(Buffer* buf, word32 sz, word32 usedSz)
|
|
{
|
|
#if 0
|
|
WLOG(WS_LOG_DEBUG, "GB: buf = %p", buf);
|
|
WLOG(WS_LOG_DEBUG, "GB: sz = %d", sz);
|
|
WLOG(WS_LOG_DEBUG, "GB: usedSz = %d", usedSz);
|
|
#endif
|
|
/* New buffer will end up being sz+usedSz long
|
|
* empty space at the head of the buffer will be compressed */
|
|
if (buf != NULL) {
|
|
word32 newSz = sz + usedSz;
|
|
/*WLOG(WS_LOG_DEBUG, "GB: newSz = %d", newSz);*/
|
|
|
|
if (newSz > buf->bufferSz) {
|
|
byte* newBuffer = (byte*)WMALLOC(newSz,
|
|
buf->heap, DYNTYPE_BUFFER);
|
|
|
|
if (newBuffer == NULL)
|
|
return WS_MEMORY_E;
|
|
|
|
/*WLOG(WS_LOG_DEBUG, "GB: resizing buffer");*/
|
|
if (buf->length > 0)
|
|
WMEMCPY(newBuffer, buf->buffer + buf->idx, usedSz);
|
|
|
|
if (!buf->dynamicFlag)
|
|
buf->dynamicFlag = 1;
|
|
else
|
|
WFREE(buf->buffer, buf->heap, DYNTYPE_BUFFER);
|
|
|
|
buf->buffer = newBuffer;
|
|
buf->bufferSz = newSz;
|
|
buf->length = usedSz;
|
|
buf->idx = 0;
|
|
}
|
|
}
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
void ShrinkBuffer(Buffer* buf, int forcedFree)
|
|
{
|
|
WLOG(WS_LOG_DEBUG, "Entering ShrinkBuffer()");
|
|
|
|
if (buf != NULL) {
|
|
word32 usedSz = buf->length - buf->idx;
|
|
|
|
WLOG(WS_LOG_DEBUG, "SB: usedSz = %u, forcedFree = %u",
|
|
usedSz, forcedFree);
|
|
|
|
if (!forcedFree && usedSz > STATIC_BUFFER_LEN)
|
|
return;
|
|
|
|
if (!forcedFree && usedSz) {
|
|
WLOG(WS_LOG_DEBUG, "SB: shifting down");
|
|
WMEMCPY(buf->staticBuffer, buf->buffer + buf->idx, usedSz);
|
|
}
|
|
|
|
if (buf->dynamicFlag) {
|
|
WLOG(WS_LOG_DEBUG, "SB: releasing dynamic buffer");
|
|
WFREE(buf->buffer, buf->heap, DYNTYPE_BUFFER);
|
|
}
|
|
buf->dynamicFlag = 0;
|
|
buf->buffer = buf->staticBuffer;
|
|
buf->bufferSz = STATIC_BUFFER_LEN;
|
|
buf->length = forcedFree ? 0 : usedSz;
|
|
buf->idx = 0;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving ShrinkBuffer()");
|
|
}
|
|
|
|
|
|
static int Receive(WOLFSSH* ssh, byte* buf, word32 sz)
|
|
{
|
|
int recvd;
|
|
|
|
if (ssh->ctx->ioRecvCb == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Your IO Recv callback is null, please set");
|
|
return -1;
|
|
}
|
|
|
|
retry:
|
|
recvd = ssh->ctx->ioRecvCb(ssh, buf, sz, ssh->ioReadCtx);
|
|
WLOG(WS_LOG_DEBUG, "Receive: recvd = %d", recvd);
|
|
if (recvd < 0)
|
|
switch (recvd) {
|
|
case WS_CBIO_ERR_GENERAL: /* general/unknown error */
|
|
return -1;
|
|
|
|
case WS_CBIO_ERR_WANT_READ: /* want read, would block */
|
|
return WS_WANT_READ;
|
|
|
|
case WS_CBIO_ERR_CONN_RST: /* connection reset */
|
|
ssh->connReset = 1;
|
|
return -1;
|
|
|
|
case WS_CBIO_ERR_ISR: /* interrupt */
|
|
goto retry;
|
|
|
|
case WS_CBIO_ERR_CONN_CLOSE: /* peer closed connection */
|
|
ssh->isClosed = 1;
|
|
return -1;
|
|
|
|
case WS_CBIO_ERR_TIMEOUT:
|
|
return -1;
|
|
|
|
default:
|
|
return recvd;
|
|
}
|
|
|
|
return recvd;
|
|
}
|
|
|
|
|
|
static int GetInputText(WOLFSSH* ssh, byte** pEol)
|
|
{
|
|
int gotLine = 0;
|
|
int inSz = 255;
|
|
int in;
|
|
char *eol;
|
|
|
|
if (GrowBuffer(&ssh->inputBuffer, inSz, 0) < 0)
|
|
return WS_MEMORY_E;
|
|
|
|
do {
|
|
in = Receive(ssh,
|
|
ssh->inputBuffer.buffer + ssh->inputBuffer.length, inSz);
|
|
|
|
if (in == -1)
|
|
return WS_SOCKET_ERROR_E;
|
|
|
|
if (in == WS_WANT_READ)
|
|
return WS_WANT_READ;
|
|
|
|
if (in > inSz)
|
|
return WS_RECV_OVERFLOW_E;
|
|
|
|
ssh->inputBuffer.length += in;
|
|
inSz -= in;
|
|
|
|
eol = WSTRNSTR((const char*)ssh->inputBuffer.buffer, "\r\n",
|
|
ssh->inputBuffer.length);
|
|
|
|
if (eol)
|
|
gotLine = 1;
|
|
|
|
} while (!gotLine && inSz);
|
|
|
|
if (pEol)
|
|
*pEol = (byte*)eol;
|
|
|
|
return (gotLine ? WS_SUCCESS : WS_VERSION_E);
|
|
}
|
|
|
|
|
|
int wolfSSH_SendPacket(WOLFSSH* ssh)
|
|
{
|
|
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendPacket()");
|
|
|
|
if (ssh->ctx->ioSendCb == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Your IO Send callback is null, please set");
|
|
return WS_SOCKET_ERROR_E;
|
|
}
|
|
|
|
while (ssh->outputBuffer.length > 0) {
|
|
int sent = ssh->ctx->ioSendCb(ssh,
|
|
ssh->outputBuffer.buffer + ssh->outputBuffer.idx,
|
|
ssh->outputBuffer.length, ssh->ioWriteCtx);
|
|
|
|
if (sent < 0) {
|
|
switch (sent) {
|
|
case WS_CBIO_ERR_WANT_WRITE: /* want write, would block */
|
|
ssh->error = WS_WANT_WRITE;
|
|
return WS_WANT_WRITE;
|
|
|
|
case WS_CBIO_ERR_CONN_RST: /* connection reset */
|
|
ssh->connReset = 1;
|
|
break;
|
|
|
|
case WS_CBIO_ERR_CONN_CLOSE: /* peer closed connection */
|
|
ssh->isClosed = 1;
|
|
break;
|
|
}
|
|
return WS_SOCKET_ERROR_E;
|
|
}
|
|
|
|
if ((word32)sent > ssh->outputBuffer.length) {
|
|
WLOG(WS_LOG_DEBUG, "wolfSSH_SendPacket() out of bounds read");
|
|
return WS_SEND_OOB_READ_E;
|
|
}
|
|
|
|
ssh->outputBuffer.idx += sent;
|
|
ssh->outputBuffer.length -= sent;
|
|
}
|
|
|
|
ssh->outputBuffer.idx = 0;
|
|
ssh->outputBuffer.plainSz = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "SB: Shrinking output buffer");
|
|
ShrinkBuffer(&ssh->outputBuffer, 0);
|
|
|
|
HighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int GetInputData(WOLFSSH* ssh, word32 size)
|
|
{
|
|
int in;
|
|
int inSz;
|
|
int maxLength;
|
|
int usedLength;
|
|
|
|
/* check max input length */
|
|
usedLength = ssh->inputBuffer.length - ssh->inputBuffer.idx;
|
|
maxLength = ssh->inputBuffer.bufferSz - usedLength;
|
|
inSz = (int)(size - usedLength); /* from last partial read */
|
|
#if 0
|
|
WLOG(WS_LOG_DEBUG, "GID: size = %u", size);
|
|
WLOG(WS_LOG_DEBUG, "GID: usedLength = %d", usedLength);
|
|
WLOG(WS_LOG_DEBUG, "GID: maxLength = %d", maxLength);
|
|
WLOG(WS_LOG_DEBUG, "GID: inSz = %d", inSz);
|
|
#endif
|
|
/*
|
|
* usedLength - how much untouched data is in the buffer
|
|
* maxLength - how much empty space is in the buffer
|
|
* inSz - difference between requested data and empty space in the buffer
|
|
* how much more we need to allocate
|
|
*/
|
|
|
|
if (inSz <= 0)
|
|
return WS_SUCCESS;
|
|
|
|
/*
|
|
* If we need more space than there is left in the buffer grow buffer.
|
|
* Growing the buffer also compresses empty space at the head of the
|
|
* buffer and resets idx to 0.
|
|
*/
|
|
if (inSz > maxLength) {
|
|
if (GrowBuffer(&ssh->inputBuffer, size, usedLength) < 0) {
|
|
ssh->error = WS_MEMORY_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Put buffer data at start if not there */
|
|
/* Compress the buffer if needed, i.e. buffer idx is non-zero */
|
|
if (usedLength > 0 && ssh->inputBuffer.idx != 0) {
|
|
WMEMMOVE(ssh->inputBuffer.buffer,
|
|
ssh->inputBuffer.buffer + ssh->inputBuffer.idx,
|
|
usedLength);
|
|
}
|
|
|
|
/* remove processed data */
|
|
ssh->inputBuffer.idx = 0;
|
|
ssh->inputBuffer.length = usedLength;
|
|
|
|
/* read data from network */
|
|
do {
|
|
in = Receive(ssh,
|
|
ssh->inputBuffer.buffer + ssh->inputBuffer.length, inSz);
|
|
if (in == -1) {
|
|
ssh->error = WS_SOCKET_ERROR_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
if (in == WS_WANT_READ) {
|
|
ssh->error = WS_WANT_READ;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
if (in > inSz) {
|
|
ssh->error = WS_RECV_OVERFLOW_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
if (in >= 0) {
|
|
ssh->inputBuffer.length += in;
|
|
inSz -= in;
|
|
}
|
|
else {
|
|
/* all other unexpected negative values is a failure case */
|
|
ssh->error = WS_FATAL_ERROR;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
} while (ssh->inputBuffer.length < size);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int GetBoolean(byte* v, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int result = WS_BUFFER_E;
|
|
|
|
if (*idx < len) {
|
|
*v = buf[*idx];
|
|
*idx += BOOLEAN_SZ;
|
|
result = WS_SUCCESS;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static int GetUint32(word32* v, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int result = WS_BUFFER_E;
|
|
|
|
if (*idx < len && UINT32_SZ <= len - *idx) {
|
|
ato32(buf + *idx, v);
|
|
*idx += UINT32_SZ;
|
|
result = WS_SUCCESS;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Gets the size of the mpint, and puts the pointer to the start of
|
|
* buf's number into *mpint. This function does not copy. */
|
|
static int GetMpint(word32* mpintSz, byte** mpint,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
int result;
|
|
|
|
result = GetUint32(mpintSz, buf, len, idx);
|
|
|
|
if (result == WS_SUCCESS) {
|
|
result = WS_BUFFER_E;
|
|
|
|
if (*idx < len && *mpintSz <= len - *idx) {
|
|
*mpint = buf + *idx;
|
|
*idx += *mpintSz;
|
|
result = WS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Gets the size of a string, copies it as much of it as will fit in
|
|
* the provided buffer, and terminates it with a NULL. */
|
|
static int GetString(char* s, word32* sSz,
|
|
byte* buf, word32 len, word32 *idx)
|
|
{
|
|
int result;
|
|
word32 strSz;
|
|
|
|
result = GetUint32(&strSz, buf, len, idx);
|
|
|
|
if (result == WS_SUCCESS) {
|
|
result = WS_BUFFER_E;
|
|
if (*idx < len && strSz <= len - *idx) {
|
|
*sSz = (strSz >= *sSz) ? *sSz - 1 : strSz; /* -1 for null char */
|
|
WMEMCPY(s, buf + *idx, *sSz);
|
|
*idx += strSz;
|
|
s[*sSz] = 0;
|
|
result = WS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Gets the size of a string, allocates memory to hold it plus a NULL, then
|
|
* copies it into the allocated buffer, and terminates it with a NULL. */
|
|
static int GetStringAlloc(WOLFSSH* ssh, char** s,
|
|
byte* buf, word32 len, word32 *idx)
|
|
{
|
|
int result;
|
|
char* str;
|
|
word32 strSz;
|
|
|
|
result = GetUint32(&strSz, buf, len, idx);
|
|
|
|
if (result == WS_SUCCESS) {
|
|
if (*idx >= len || strSz > len - *idx)
|
|
return WS_BUFFER_E;
|
|
str = (char*)WMALLOC(strSz + 1, ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (str == NULL)
|
|
return WS_MEMORY_E;
|
|
WMEMCPY(str, buf + *idx, strSz);
|
|
*idx += strSz;
|
|
str[strSz] = '\0';
|
|
*s = str;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static int GetNameList(byte* idList, word32* idListSz,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
byte idListIdx;
|
|
word32 nameListSz, nameListIdx;
|
|
word32 begin;
|
|
byte* name;
|
|
word32 nameSz;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering GetNameList()");
|
|
|
|
if (idList == NULL || idListSz == NULL ||
|
|
buf == NULL || len == 0 || idx == NULL) {
|
|
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
/*
|
|
* This iterates across a name list and finds names that end in either the
|
|
* comma delimeter or with the end of the list.
|
|
*/
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
if (begin >= len || begin + 4 >= len)
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&nameListSz, buf, len, &begin);
|
|
|
|
/* The strings we want are now in the bounds of the message, and the
|
|
* length of the list. Find the commas, or end of list, and then decode
|
|
* the values. */
|
|
if (ret == WS_SUCCESS) {
|
|
name = buf + begin;
|
|
nameSz = 0;
|
|
nameListIdx = 0;
|
|
idListIdx = 0;
|
|
|
|
while (nameListIdx < nameListSz) {
|
|
nameListIdx++;
|
|
|
|
if (nameListIdx == nameListSz)
|
|
nameSz++;
|
|
|
|
if (nameListIdx + begin >= len)
|
|
return WS_BUFFER_E;
|
|
|
|
if (nameListIdx == nameListSz || name[nameSz] == ',') {
|
|
byte id;
|
|
|
|
id = NameToId((char*)name, nameSz);
|
|
{
|
|
const char* displayName = IdToName(id);
|
|
if (displayName) {
|
|
WLOG(WS_LOG_DEBUG, "DNL: name ID = %s", displayName);
|
|
}
|
|
}
|
|
if (id != ID_UNKNOWN) {
|
|
if (idListIdx >= *idListSz) {
|
|
WLOG(WS_LOG_ERROR, "No more space left for names");
|
|
return WS_BUFFER_E;
|
|
}
|
|
idList[idListIdx++] = id;
|
|
}
|
|
|
|
name += 1 + nameSz;
|
|
nameSz = 0;
|
|
}
|
|
else
|
|
nameSz++;
|
|
}
|
|
|
|
begin += nameListSz;
|
|
*idListSz = idListIdx;
|
|
*idx = begin;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving GetNameList(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
static const byte cannedEncAlgo[] = {
|
|
#ifdef HAVE_AESGCM
|
|
ID_AES128_GCM,
|
|
#endif
|
|
#ifdef WOLFSSL_AES_COUNTER
|
|
ID_AES128_CTR,
|
|
#endif
|
|
#ifdef HAVE_AES_CBC
|
|
ID_AES128_CBC,
|
|
#endif
|
|
};
|
|
|
|
#if defined(NO_HMAC) || defined(NO_SHA256)
|
|
#define WOLFSSH_NO_HMAC_SHA2_256
|
|
#endif
|
|
#if defined(NO_HMAC) || defined(NO_SHA)
|
|
#define WOLFSSH_NO_HMAC_SHA1_96
|
|
#endif
|
|
#if defined(NO_HMAC) || defined(NO_SHA)
|
|
#define WOLFSSH_NO_HMAC_SHA1
|
|
#endif
|
|
|
|
static const byte cannedMacAlgo[] = {
|
|
#ifndef WOLFSSH_NO_HMAC_SHA2_256
|
|
ID_HMAC_SHA2_256,
|
|
#endif
|
|
#ifndef WOLFSSH_NO_HMAC_SHA1_96
|
|
ID_HMAC_SHA1_96,
|
|
#endif
|
|
#ifndef WOLFSSH_NO_HMAC_SHA1
|
|
ID_HMAC_SHA1,
|
|
#endif
|
|
};
|
|
static const byte cannedKeyAlgoRsa[] = {ID_SSH_RSA};
|
|
static const byte cannedKeyAlgoEcc256[] = {ID_ECDSA_SHA2_NISTP256};
|
|
static const byte cannedKeyAlgoEcc384[] = {ID_ECDSA_SHA2_NISTP384};
|
|
static const byte cannedKeyAlgoEcc521[] = {ID_ECDSA_SHA2_NISTP521};
|
|
|
|
#if !defined(HAVE_ECC) || defined(NO_SHA256)
|
|
#define WOLFSSH_NO_ECDH_SHA2_NISTP256
|
|
#endif
|
|
#if defined(NO_DH) || defined(NO_SHA256)
|
|
#define WOLFSSH_NO_DH_GEX_SHA256
|
|
#endif
|
|
#if defined(NO_DH) || defined(NO_SHA)
|
|
#define WOLFSSH_NO_DH_GROUP14_SHA1
|
|
#endif
|
|
#if defined(NO_DH) || defined(NO_SHA)
|
|
#define WOLFSSH_NO_DH_GROUP1_SHA1
|
|
#endif
|
|
|
|
static const byte cannedKexAlgo[] = {
|
|
#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP256
|
|
ID_ECDH_SHA2_NISTP256,
|
|
#endif
|
|
#ifndef WOLFSSH_NO_DH_GEX_SHA256
|
|
ID_DH_GEX_SHA256,
|
|
#endif
|
|
#ifndef WOLFSSH_NO_DH_GROUP14_SHA1
|
|
ID_DH_GROUP14_SHA1,
|
|
#endif
|
|
#ifndef WOLFSSH_NO_DH_GROUP1_SHA1
|
|
ID_DH_GROUP1_SHA1,
|
|
#endif
|
|
};
|
|
|
|
static const word32 cannedEncAlgoSz = sizeof(cannedEncAlgo);
|
|
static const word32 cannedMacAlgoSz = sizeof(cannedMacAlgo);
|
|
static const word32 cannedKeyAlgoRsaSz = sizeof(cannedKeyAlgoRsa);
|
|
static const word32 cannedKeyAlgoEcc256Sz = sizeof(cannedKeyAlgoEcc256);
|
|
static const word32 cannedKeyAlgoEcc384Sz = sizeof(cannedKeyAlgoEcc384);
|
|
static const word32 cannedKeyAlgoEcc521Sz = sizeof(cannedKeyAlgoEcc521);
|
|
static const word32 cannedKexAlgoSz = sizeof(cannedKexAlgo);
|
|
|
|
|
|
static byte MatchIdLists(int side, const byte* left, word32 leftSz,
|
|
const byte* right, word32 rightSz)
|
|
{
|
|
word32 i, j;
|
|
|
|
/* When matching on the client, swap left and right. Left should be
|
|
* the client's list and right should be the server's list. */
|
|
if (side == WOLFSSH_ENDPOINT_CLIENT) {
|
|
const byte* swap = left;
|
|
word32 swapSz = leftSz;
|
|
|
|
left = right;
|
|
right = swap;
|
|
leftSz = rightSz;
|
|
rightSz = swapSz;
|
|
}
|
|
|
|
if (left != NULL && leftSz > 0 && right != NULL && rightSz > 0) {
|
|
for (i = 0; i < leftSz; i++) {
|
|
for (j = 0; j < rightSz; j++) {
|
|
if (left[i] == right[j]) {
|
|
#if 0
|
|
WLOG(WS_LOG_DEBUG, "MID: matched %s", IdToName(left[i]));
|
|
#endif
|
|
return left[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ID_UNKNOWN;
|
|
}
|
|
|
|
|
|
static INLINE byte BlockSzForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_AES128_CBC:
|
|
case ID_AES128_CTR:
|
|
case ID_AES128_GCM:
|
|
return AES_BLOCK_SIZE;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE byte MacSzForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_HMAC_SHA1:
|
|
return SHA_DIGEST_SIZE;
|
|
case ID_HMAC_SHA1_96:
|
|
return SHA1_96_SZ;
|
|
case ID_HMAC_SHA2_256:
|
|
return SHA256_DIGEST_SIZE;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE byte KeySzForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_HMAC_SHA1:
|
|
case ID_HMAC_SHA1_96:
|
|
return SHA_DIGEST_SIZE;
|
|
case ID_HMAC_SHA2_256:
|
|
return SHA256_DIGEST_SIZE;
|
|
case ID_AES128_CBC:
|
|
case ID_AES128_CTR:
|
|
case ID_AES128_GCM:
|
|
return AES_BLOCK_SIZE;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE enum wc_HashType HashForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_DH_GROUP1_SHA1:
|
|
case ID_DH_GROUP14_SHA1:
|
|
case ID_SSH_RSA:
|
|
return WC_HASH_TYPE_SHA;
|
|
case ID_DH_GEX_SHA256:
|
|
case ID_ECDH_SHA2_NISTP256:
|
|
case ID_ECDSA_SHA2_NISTP256:
|
|
return WC_HASH_TYPE_SHA256;
|
|
case ID_ECDH_SHA2_NISTP384:
|
|
case ID_ECDSA_SHA2_NISTP384:
|
|
return WC_HASH_TYPE_SHA384;
|
|
case ID_ECDH_SHA2_NISTP521:
|
|
case ID_ECDSA_SHA2_NISTP521:
|
|
return WC_HASH_TYPE_SHA512;
|
|
default:
|
|
return WC_HASH_TYPE_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE int wcPrimeForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_ECDH_SHA2_NISTP256:
|
|
case ID_ECDSA_SHA2_NISTP256:
|
|
return ECC_SECP256R1;
|
|
case ID_ECDH_SHA2_NISTP384:
|
|
case ID_ECDSA_SHA2_NISTP384:
|
|
return ECC_SECP384R1;
|
|
case ID_ECDH_SHA2_NISTP521:
|
|
case ID_ECDSA_SHA2_NISTP521:
|
|
return ECC_SECP521R1;
|
|
default:
|
|
return ECC_CURVE_INVALID;
|
|
}
|
|
}
|
|
static INLINE const char *PrimeNameForId(byte id)
|
|
{
|
|
switch (id) {
|
|
case ID_ECDH_SHA2_NISTP256:
|
|
case ID_ECDSA_SHA2_NISTP256:
|
|
return "nistp256";
|
|
case ID_ECDH_SHA2_NISTP384:
|
|
case ID_ECDSA_SHA2_NISTP384:
|
|
return "nistp384";
|
|
case ID_ECDH_SHA2_NISTP521:
|
|
case ID_ECDSA_SHA2_NISTP521:
|
|
return "nistp521";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE byte AeadModeForId(byte id)
|
|
{
|
|
return (id == ID_AES128_GCM);
|
|
}
|
|
|
|
|
|
static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
int side;
|
|
byte algoId;
|
|
byte list[6];
|
|
word32 listSz;
|
|
word32 skipSz;
|
|
word32 begin;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoKexInit()");
|
|
|
|
if (ssh == NULL || ssh->ctx == NULL ||
|
|
buf == NULL || len == 0 || idx == NULL) {
|
|
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
/*
|
|
* I don't need to save what the client sends here. I should decode
|
|
* each list into a local array of IDs, and pick the one the peer is
|
|
* using that's on my known list, or verify that the one the peer can
|
|
* support the other direction is on my known list. All I need to do
|
|
* is save the actual values.
|
|
*/
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->handshake == NULL) {
|
|
ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);
|
|
if (ssh->handshake == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Couldn't allocate handshake info");
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
side = ssh->ctx->side;
|
|
|
|
/* Check that the cookie exists inside the message */
|
|
if (begin + COOKIE_SZ > len) {
|
|
/* error, out of bounds */
|
|
ret = WS_PARSE_E;
|
|
}
|
|
else {
|
|
/* Move past the cookie. */
|
|
begin += COOKIE_SZ;
|
|
}
|
|
}
|
|
|
|
/* KEX Algorithms */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: KEX Algorithms");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
algoId = MatchIdLists(side, list, listSz,
|
|
cannedKexAlgo, cannedKexAlgoSz);
|
|
if (algoId == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate KEX Algo");
|
|
ret = WS_MATCH_KEX_ALGO_E;
|
|
}
|
|
else {
|
|
ssh->handshake->kexId = algoId;
|
|
ssh->handshake->hashId = HashForId(algoId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Server Host Key Algorithms */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Server Host Key Algorithms");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
const byte *cannedKeyAlgo;
|
|
word32 cannedKeyAlgoSz;
|
|
|
|
switch (ssh->ctx->useEcc) {
|
|
case ECC_SECP256R1:
|
|
cannedKeyAlgo = cannedKeyAlgoEcc256;
|
|
cannedKeyAlgoSz = cannedKeyAlgoEcc256Sz;
|
|
break;
|
|
case ECC_SECP384R1:
|
|
cannedKeyAlgo = cannedKeyAlgoEcc384;
|
|
cannedKeyAlgoSz = cannedKeyAlgoEcc384Sz;
|
|
break;
|
|
case ECC_SECP521R1:
|
|
cannedKeyAlgo = cannedKeyAlgoEcc521;
|
|
cannedKeyAlgoSz = cannedKeyAlgoEcc521Sz;
|
|
break;
|
|
default:
|
|
cannedKeyAlgo = cannedKeyAlgoRsa;
|
|
cannedKeyAlgoSz = cannedKeyAlgoRsaSz;
|
|
}
|
|
algoId = MatchIdLists(side, list, listSz,
|
|
cannedKeyAlgo, cannedKeyAlgoSz);
|
|
if (algoId == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate Server Host Key Algo");
|
|
return WS_MATCH_KEY_ALGO_E;
|
|
}
|
|
else
|
|
ssh->handshake->pubKeyId = algoId;
|
|
}
|
|
}
|
|
|
|
/* Enc Algorithms - Client to Server */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Enc Algorithms - Client to Server");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
algoId = MatchIdLists(side, list, listSz,
|
|
cannedEncAlgo, cannedEncAlgoSz);
|
|
if (algoId == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo C2S");
|
|
ret = WS_MATCH_ENC_ALGO_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Enc Algorithms - Server to Client */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Enc Algorithms - Server to Client");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (MatchIdLists(side, list, listSz, &algoId, 1) == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo S2C");
|
|
ret = WS_MATCH_ENC_ALGO_E;
|
|
}
|
|
else {
|
|
ssh->handshake->encryptId = algoId;
|
|
ssh->handshake->aeadMode = AeadModeForId(algoId);
|
|
ssh->handshake->blockSz = BlockSzForId(algoId);
|
|
ssh->handshake->keys.encKeySz =
|
|
ssh->handshake->peerKeys.encKeySz =
|
|
KeySzForId(algoId);
|
|
if (!ssh->handshake->aeadMode) {
|
|
ssh->handshake->keys.ivSz =
|
|
ssh->handshake->peerKeys.ivSz =
|
|
ssh->handshake->blockSz;
|
|
}
|
|
else {
|
|
ssh->handshake->keys.ivSz =
|
|
ssh->handshake->peerKeys.ivSz =
|
|
AEAD_NONCE_SZ;
|
|
ssh->handshake->macSz = ssh->handshake->blockSz;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* MAC Algorithms - Client to Server */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: MAC Algorithms - Client to Server");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS && !ssh->aeadMode) {
|
|
algoId = MatchIdLists(side, list, listSz,
|
|
cannedMacAlgo, cannedMacAlgoSz);
|
|
if (algoId == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo C2S");
|
|
ret = WS_MATCH_ENC_ALGO_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* MAC Algorithms - Server to Client */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: MAC Algorithms - Server to Client");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS && !ssh->handshake->aeadMode) {
|
|
if (MatchIdLists(side, list, listSz, &algoId, 1) == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
|
|
ret = WS_MATCH_MAC_ALGO_E;
|
|
}
|
|
else {
|
|
ssh->handshake->macId = algoId;
|
|
ssh->handshake->macSz = MacSzForId(algoId);
|
|
ssh->handshake->keys.macKeySz =
|
|
ssh->handshake->peerKeys.macKeySz =
|
|
KeySzForId(algoId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compression Algorithms - Client to Server */
|
|
if (ret == WS_SUCCESS) {
|
|
/* The compression algorithm lists should have none as a value. */
|
|
algoId = ID_NONE;
|
|
|
|
WLOG(WS_LOG_DEBUG, "DKI: Compression Algorithms - Client to Server");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
if (MatchIdLists(side, list, listSz, &algoId, 1) == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate Compression Algo C2S");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compression Algorithms - Server to Client */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Compression Algorithms - Server to Client");
|
|
listSz = sizeof(list);
|
|
ret = GetNameList(list, &listSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
if (MatchIdLists(side, list, listSz, &algoId, 1) == ID_UNKNOWN) {
|
|
WLOG(WS_LOG_DEBUG, "Unable to negotiate Compression Algo S2C");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Languages - Client to Server, skip */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Languages - Client to Server");
|
|
ret = GetUint32(&skipSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
begin += skipSz;
|
|
}
|
|
|
|
/* Languages - Server to Client, skip */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: Languages - Server to Client");
|
|
ret = GetUint32(&skipSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
begin += skipSz;
|
|
}
|
|
|
|
/* First KEX Packet Follows */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: KEX Packet Follows");
|
|
ret = GetBoolean(&ssh->handshake->kexPacketFollows, buf, len, &begin);
|
|
}
|
|
|
|
/* Skip the "for future use" length. */
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DKI: For Future Use");
|
|
ret = GetUint32(&skipSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
begin += skipSz;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
byte scratchLen[LENGTH_SZ];
|
|
word32 strSz;
|
|
|
|
if (!ssh->isKeying) {
|
|
WLOG(WS_LOG_DEBUG, "Keying initiated");
|
|
ret = SendKexInit(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashInit(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->peerProtoId, ssh->peerProtoIdSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
strSz = (word32)WSTRLEN(sshProtoIdStr) - SSH_PROTO_EOL_SZ;
|
|
c32toa(strSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
(const byte*)sshProtoIdStr, strSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->peerProtoId, ssh->peerProtoIdSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->kexInit,
|
|
ssh->handshake->kexInitSz);
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
c32toa(len + 1, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
scratchLen[0] = MSGID_KEXINIT;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, MSG_ID_SZ);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
buf, len);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->kexInit,
|
|
ssh->handshake->kexInitSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)
|
|
ssh->clientState = CLIENT_KEXINIT_DONE;
|
|
else
|
|
ssh->serverState = SERVER_KEXINIT_DONE;
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoKexInit(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const byte dhGenerator[] = { 2 };
|
|
static const byte dhPrimeGroup1[] = {
|
|
/* SSH DH Group 1 (Oakley Group 2, 1024-bit MODP Group, RFC 2409) */
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
|
|
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
|
|
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
|
|
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
|
|
0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
|
|
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
|
|
0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
|
|
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
|
|
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
|
|
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
|
|
0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
|
|
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
|
|
0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
|
|
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
static const byte dhPrimeGroup14[] = {
|
|
/* SSH DH Group 14 (Oakley Group 14, 2048-bit MODP Group, RFC 3526) */
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
|
|
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
|
|
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
|
|
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
|
|
0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
|
|
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
|
|
0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
|
|
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
|
|
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
|
|
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
|
|
0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
|
|
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
|
|
0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
|
|
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
|
|
0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
|
|
0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A,
|
|
0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
|
|
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96,
|
|
0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
|
|
0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
|
|
0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
|
|
0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C,
|
|
0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
|
|
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03,
|
|
0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
|
|
0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9,
|
|
0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
|
|
0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5,
|
|
0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
|
|
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
|
|
static const word32 dhGeneratorSz = sizeof(dhGenerator);
|
|
static const word32 dhPrimeGroup1Sz = sizeof(dhPrimeGroup1);
|
|
static const word32 dhPrimeGroup14Sz = sizeof(dhPrimeGroup14);
|
|
|
|
|
|
static int DoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
/* First get the length of the MP_INT, and then add in the hash of the
|
|
* mp_int value of e as it appears in the packet. After that, decode e
|
|
* into an mp_int struct for the DH calculation by wolfCrypt.
|
|
*
|
|
* This function also works as MSGID_KEXECDH_INIT (30). That message
|
|
* has the same format as MSGID_KEXDH_INIT, except it is the ECDH Q value
|
|
* in the message isn't of the DH e value. Treat the Q as e. */
|
|
/* DYNTYPE_DH */
|
|
|
|
byte* e;
|
|
word32 eSz;
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || ssh->handshake == NULL || buf == NULL || len == 0 ||
|
|
idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetUint32(&eSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
e = buf + begin;
|
|
begin += eSz;
|
|
|
|
if (eSz <= sizeof(ssh->handshake->e)) {
|
|
WMEMCPY(ssh->handshake->e, e, eSz);
|
|
ssh->handshake->eSz = eSz;
|
|
}
|
|
|
|
ssh->clientState = CLIENT_KEXDH_INIT_DONE;
|
|
*idx = begin;
|
|
|
|
ret = SendKexDhReply(ssh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
byte* pubKey = NULL;
|
|
word32 pubKeySz;
|
|
byte* f = NULL;
|
|
word32 fSz;
|
|
byte* sig;
|
|
word32 sigSz;
|
|
word32 scratch;
|
|
byte scratchLen[LENGTH_SZ];
|
|
byte kPad = 0;
|
|
struct {
|
|
byte useRsa;
|
|
word32 keySz;
|
|
union {
|
|
struct {
|
|
RsaKey key;
|
|
} rsa;
|
|
struct {
|
|
ecc_key key;
|
|
} ecc;
|
|
} sk;
|
|
} sigKeyBlock;
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
int tmpIdx = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoKexDhReply()");
|
|
|
|
if (ssh == NULL || ssh->handshake == NULL || buf == NULL ||
|
|
len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS && len < LENGTH_SZ*2 + *idx) {
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetUint32(&pubKeySz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS && (pubKeySz > len - LENGTH_SZ - begin )) {
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
pubKey = buf + begin;
|
|
if (ssh->ctx->publicKeyCheckCb != NULL) {
|
|
WLOG(WS_LOG_DEBUG, "DKDR: Calling the public key check callback");
|
|
ret = ssh->ctx->publicKeyCheckCb(pubKey, pubKeySz,
|
|
ssh->publicKeyCheckCtx);
|
|
if (ret == 0) {
|
|
WLOG(WS_LOG_DEBUG, "DKDR: public key accepted");
|
|
ret = WS_SUCCESS;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "DKDR: public key rejected");
|
|
ret = WS_PUBKEY_REJECTED_E;
|
|
}
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "DKDR: no public key check callback, accepted");
|
|
ret = WS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
/* Hash in the raw public key blob from the server including its
|
|
* length which is at LENGTH_SZ offset ahead of pubKey. */
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
pubKey - LENGTH_SZ, pubKeySz + LENGTH_SZ);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
begin += pubKeySz;
|
|
|
|
/* If using DH-GEX include the GEX specific values. */
|
|
if (ret == WS_SUCCESS && ssh->handshake->kexId == ID_DH_GEX_SHA256) {
|
|
byte primeGroupPad = 0, generatorPad = 0;
|
|
|
|
/* Hash in the client's requested minimum key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexMinSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's requested preferred key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexPreferredSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's requested maximum key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexMaxSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Add a pad byte if the mpint has the MSB set. */
|
|
if (ret == 0) {
|
|
if (ssh->handshake->primeGroup != NULL &&
|
|
ssh->handshake->primeGroup[0] & 0x80)
|
|
primeGroupPad = 1;
|
|
|
|
/* Hash in the length of the GEX prime group. */
|
|
c32toa(ssh->handshake->primeGroupSz + primeGroupPad,
|
|
scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the GEX prime group. */
|
|
if (ret == 0) {
|
|
if (primeGroupPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the GEX prime group. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->primeGroup,
|
|
ssh->handshake->primeGroupSz);
|
|
/* Add a pad byte if the mpint has the MSB set. */
|
|
if (ret == 0) {
|
|
if (ssh->handshake->generator[0] & 0x80)
|
|
generatorPad = 1;
|
|
|
|
/* Hash in the length of the GEX generator. */
|
|
c32toa(ssh->handshake->generatorSz + generatorPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the GEX generator. */
|
|
if (ret == 0) {
|
|
if (generatorPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the GEX generator. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->generator,
|
|
ssh->handshake->generatorSz);
|
|
}
|
|
|
|
/* Hash in the size of the client's DH e-value (ECDH Q-value). */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->eSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's DH e-value (ECDH Q-value). */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->e, ssh->handshake->eSz);
|
|
|
|
/* Get and hash in the server's DH f-value (ECDH Q-value) */
|
|
if (ret == WS_SUCCESS) {
|
|
f = buf + begin;
|
|
ret = GetUint32(&fSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (fSz > len - begin) {
|
|
WLOG(WS_LOG_DEBUG, "F size would result in error");
|
|
ret = WS_PARSE_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
f, fSz + LENGTH_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
f = buf + begin;
|
|
begin += fSz;
|
|
ret = GetUint32(&sigSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (sigSz > len - begin) {
|
|
WLOG(WS_LOG_DEBUG, "Signature size would result in error 1");
|
|
ret = WS_PARSE_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
sig = buf + begin;
|
|
tmpIdx = begin;
|
|
begin += sigSz;
|
|
*idx = begin;
|
|
|
|
/* Load in the server's public signing key */
|
|
sigKeyBlock.useRsa = ssh->handshake->pubKeyId == ID_SSH_RSA;
|
|
|
|
if (sigKeyBlock.useRsa) {
|
|
byte* e;
|
|
word32 eSz;
|
|
byte* n;
|
|
word32 nSz;
|
|
word32 pubKeyIdx = 0;
|
|
|
|
ret = wc_InitRsaKey(&sigKeyBlock.sk.rsa.key, ssh->ctx->heap);
|
|
if (ret != 0)
|
|
ret = WS_RSA_E;
|
|
if (ret == 0)
|
|
ret = GetUint32(&scratch, pubKey, pubKeySz, &pubKeyIdx);
|
|
/* This is the algo name. */
|
|
if (ret == WS_SUCCESS) {
|
|
pubKeyIdx += scratch;
|
|
ret = GetUint32(&eSz, pubKey, pubKeySz, &pubKeyIdx);
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
e = pubKey + pubKeyIdx;
|
|
pubKeyIdx += eSz;
|
|
ret = GetUint32(&nSz, pubKey, pubKeySz, &pubKeyIdx);
|
|
if (ret == WS_SUCCESS && (nSz + pubKeyIdx > len)) {
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
n = pubKey + pubKeyIdx;
|
|
ret = wc_RsaPublicKeyDecodeRaw(n, nSz, e, eSz,
|
|
&sigKeyBlock.sk.rsa.key);
|
|
}
|
|
|
|
if (ret == 0)
|
|
sigKeyBlock.keySz = sizeof(sigKeyBlock.sk.rsa.key);
|
|
else
|
|
ret = WS_RSA_E;
|
|
}
|
|
else {
|
|
ret = wc_ecc_init_ex(&sigKeyBlock.sk.ecc.key, ssh->ctx->heap,
|
|
INVALID_DEVID);
|
|
if (ret == 0)
|
|
ret = wc_ecc_import_x963(pubKey, pubKeySz,
|
|
&sigKeyBlock.sk.ecc.key);
|
|
if (ret == 0)
|
|
sigKeyBlock.keySz = sizeof(sigKeyBlock.sk.ecc.key);
|
|
else
|
|
ret = WS_ECC_E;
|
|
}
|
|
|
|
/* Generate and hash in the shared secret */
|
|
if (ret == 0) {
|
|
if (!ssh->handshake->useEcc) {
|
|
ret = wc_DhAgree(&ssh->handshake->privKey.dh,
|
|
ssh->k, &ssh->kSz,
|
|
ssh->handshake->x, ssh->handshake->xSz,
|
|
f, fSz);
|
|
ForceZero(ssh->handshake->x, ssh->handshake->xSz);
|
|
wc_FreeDhKey(&ssh->handshake->privKey.dh);
|
|
}
|
|
else {
|
|
ecc_key key;
|
|
ret = wc_ecc_init(&key);
|
|
if (ret == 0)
|
|
ret = wc_ecc_import_x963(f, fSz, &key);
|
|
if (ret == 0)
|
|
ret = wc_ecc_shared_secret(&ssh->handshake->privKey.ecc,
|
|
&key, ssh->k, &ssh->kSz);
|
|
wc_ecc_free(&key);
|
|
wc_ecc_free(&ssh->handshake->privKey.ecc);
|
|
}
|
|
}
|
|
CreateMpint(ssh->k, &ssh->kSz, &kPad);
|
|
|
|
/* Hash in the shared secret K. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->kSz + kPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
if (ret == 0) {
|
|
if (kPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, scratchLen, 1);
|
|
}
|
|
}
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->k, ssh->kSz);
|
|
|
|
/* Save the exchange hash value H, and session ID. */
|
|
if (ret == 0)
|
|
ret = wc_HashFinal(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, ssh->h);
|
|
if (ret == 0) {
|
|
ssh->hSz = wc_HashGetDigestSize((enum wc_HashType)ssh->handshake->hashId);
|
|
if (ssh->sessionIdSz == 0) {
|
|
WMEMCPY(ssh->sessionId, ssh->h, ssh->hSz);
|
|
ssh->sessionIdSz = ssh->hSz;
|
|
}
|
|
}
|
|
|
|
if (ret != WS_SUCCESS)
|
|
ret = WS_CRYPTO_FAILED;
|
|
|
|
/* Verify h with the server's public key. */
|
|
if (ret == WS_SUCCESS) {
|
|
/* Skip past the sig name. Check it, though. Other SSH
|
|
* implementations do the verify based on the name, despite what
|
|
* was agreed upon. XXX*/
|
|
begin = 0;
|
|
ret = GetUint32(&scratch, sig, sigSz, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
/* Check that scratch isn't larger than the remainder of the
|
|
* sig buffer and leaves enough room for another length. */
|
|
if (scratch > sigSz - begin - LENGTH_SZ) {
|
|
WLOG(WS_LOG_DEBUG, "sig name size is too large");
|
|
ret = WS_PARSE_E;
|
|
}
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
begin += scratch;
|
|
ret = GetUint32(&scratch, sig, sigSz, &begin);
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
if (scratch > sigSz - begin) {
|
|
WLOG(WS_LOG_DEBUG, "sig name size is too large");
|
|
ret = WS_PARSE_E;
|
|
}
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
sig = sig + begin;
|
|
sigSz = scratch;
|
|
|
|
if (sigSz + begin + tmpIdx > len) {
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Signature size found would result in error 2");
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_SignatureVerify(
|
|
HashForId(ssh->handshake->pubKeyId),
|
|
sigKeyBlock.useRsa ?
|
|
WC_SIGNATURE_TYPE_RSA_W_ENC :
|
|
WC_SIGNATURE_TYPE_ECC,
|
|
ssh->h, ssh->hSz, sig, sigSz,
|
|
&sigKeyBlock.sk, sigKeyBlock.keySz);
|
|
if (ret != 0) {
|
|
WLOG(WS_LOG_DEBUG,
|
|
"DoKexDhReply: Signature Verify fail (%d)", ret);
|
|
ret = sigKeyBlock.useRsa ? WS_RSA_E : WS_ECC_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sigKeyBlock.useRsa)
|
|
wc_FreeRsaKey(&sigKeyBlock.sk.rsa.key);
|
|
else
|
|
wc_ecc_free(&sigKeyBlock.sk.ecc.key);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKeys(ssh);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = SendNewKeys(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoKexDhReply(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
(void)buf;
|
|
(void)len;
|
|
(void)idx;
|
|
|
|
if (ssh == NULL || ssh->handshake == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->peerEncryptId = ssh->handshake->encryptId;
|
|
ssh->peerMacId = ssh->handshake->macId;
|
|
ssh->peerBlockSz = ssh->handshake->blockSz;
|
|
ssh->peerMacSz = ssh->handshake->macSz;
|
|
ssh->peerAeadMode = ssh->handshake->aeadMode;
|
|
WMEMCPY(&ssh->peerKeys, &ssh->handshake->peerKeys, sizeof(Keys));
|
|
|
|
switch (ssh->peerEncryptId) {
|
|
case ID_NONE:
|
|
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher none");
|
|
break;
|
|
|
|
#ifdef HAVE_AES_CBC
|
|
case ID_AES128_CBC:
|
|
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher aes128-cbc");
|
|
ret = wc_AesSetKey(&ssh->decryptCipher.aes,
|
|
ssh->peerKeys.encKey, ssh->peerKeys.encKeySz,
|
|
ssh->peerKeys.iv, AES_DECRYPTION);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_AES_COUNTER
|
|
case ID_AES128_CTR:
|
|
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher aes128-ctr");
|
|
ret = wc_AesSetKey(&ssh->decryptCipher.aes,
|
|
ssh->peerKeys.encKey, ssh->peerKeys.encKeySz,
|
|
ssh->peerKeys.iv, AES_ENCRYPTION);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef HAVE_AESGCM
|
|
case ID_AES128_GCM:
|
|
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher aes128-gcm");
|
|
ret = wc_AesGcmSetKey(&ssh->decryptCipher.aes,
|
|
ssh->peerKeys.encKey,
|
|
ssh->peerKeys.encKeySz);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher invalid");
|
|
break;
|
|
}
|
|
|
|
if (ret == 0)
|
|
ret = WS_SUCCESS;
|
|
else
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->rxCount = 0;
|
|
ssh->highwaterFlag = 0;
|
|
ssh->isKeying = 0;
|
|
HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);
|
|
ssh->handshake = NULL;
|
|
WLOG(WS_LOG_DEBUG, "Keying completed");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoKexDhGexRequest(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || ssh->handshake == NULL || buf == NULL || len == 0 ||
|
|
idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetUint32(&ssh->handshake->dhGexMinSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = GetUint32(&ssh->handshake->dhGexPreferredSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = GetUint32(&ssh->handshake->dhGexMaxSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_INFO, " min = %u, preferred = %u, max = %u",
|
|
ssh->handshake->dhGexMinSz,
|
|
ssh->handshake->dhGexPreferredSz,
|
|
ssh->handshake->dhGexMaxSz);
|
|
*idx = begin;
|
|
ret = SendKexDhGexGroup(ssh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoKexDhGexGroup(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
byte* primeGroup = NULL;
|
|
word32 primeGroupSz;
|
|
byte* generator = NULL;
|
|
word32 generatorSz;
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetMpint(&primeGroupSz, &primeGroup, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetMpint(&generatorSz, &generator, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->handshake->primeGroup =
|
|
(byte*)WMALLOC(primeGroupSz + UINT32_SZ,
|
|
ssh->ctx->heap, DYNTYPE_MPINT);
|
|
if (ssh->handshake->primeGroup == NULL)
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->handshake->generator =
|
|
(byte*)WMALLOC(generatorSz + UINT32_SZ,
|
|
ssh->ctx->heap, DYNTYPE_MPINT);
|
|
if (ssh->handshake->generator == NULL) {
|
|
ret = WS_MEMORY_E;
|
|
WFREE(ssh->handshake->primeGroup, ssh->ctx->heap, DYNTYPE_MPINT);
|
|
ssh->handshake->primeGroup = NULL;
|
|
}
|
|
}
|
|
|
|
if (WS_SUCCESS) {
|
|
c32toa(primeGroupSz, ssh->handshake->primeGroup);
|
|
WMEMCPY(ssh->handshake->primeGroup + UINT32_SZ,
|
|
primeGroup, primeGroupSz);
|
|
ssh->handshake->primeGroupSz = primeGroupSz;
|
|
c32toa(generatorSz, ssh->handshake->generator);
|
|
WMEMCPY(ssh->handshake->generator + UINT32_SZ,
|
|
generator, generatorSz);
|
|
ssh->handshake->generatorSz = generatorSz;
|
|
|
|
*idx = begin;
|
|
ret = SendKexDhInit(ssh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoIgnore(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 dataSz;
|
|
word32 begin = *idx;
|
|
|
|
(void)ssh;
|
|
(void)len;
|
|
|
|
ato32(buf + begin, &dataSz);
|
|
begin += LENGTH_SZ + dataSz;
|
|
|
|
*idx = begin;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoDebug(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
byte alwaysDisplay;
|
|
char* msg = NULL;
|
|
char* lang = NULL;
|
|
word32 strSz;
|
|
word32 begin;
|
|
|
|
if (ssh == NULL || buf == NULL || idx == NULL ||
|
|
len < (2 * LENGTH_SZ) + 1) {
|
|
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
begin = *idx;
|
|
|
|
alwaysDisplay = buf[begin++];
|
|
|
|
ato32(buf + begin, &strSz);
|
|
begin += LENGTH_SZ;
|
|
if (strSz > 0) {
|
|
if (strSz > len - begin) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
msg = (char*)WMALLOC(strSz + 1, ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (msg != NULL) {
|
|
WMEMCPY(msg, buf + begin, strSz);
|
|
msg[strSz] = 0;
|
|
}
|
|
else {
|
|
return WS_MEMORY_E;
|
|
}
|
|
begin += strSz;
|
|
}
|
|
|
|
ato32(buf + begin, &strSz);
|
|
begin += LENGTH_SZ;
|
|
if (strSz > 0) {
|
|
if (strSz > len - begin) {
|
|
WFREE(msg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
lang = (char*)WMALLOC(strSz + 1, ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (lang != NULL) {
|
|
WMEMCPY(lang, buf + begin, strSz);
|
|
lang[strSz] = 0;
|
|
}
|
|
else {
|
|
WFREE(msg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
return WS_MEMORY_E;
|
|
}
|
|
begin += strSz;
|
|
}
|
|
|
|
if (alwaysDisplay) {
|
|
WLOG(WS_LOG_DEBUG, "DEBUG MSG (%s): %s",
|
|
(lang == NULL) ? "none" : lang,
|
|
(msg == NULL) ? "no message" : msg);
|
|
}
|
|
|
|
*idx = begin;
|
|
|
|
WFREE(msg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
WFREE(lang, ssh->ctx->heap, DYNTYPE_STRING);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoUnimplemented(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 seq;
|
|
word32 begin = *idx;
|
|
|
|
(void)ssh;
|
|
(void)len;
|
|
|
|
ato32(buf + begin, &seq);
|
|
begin += UINT32_SZ;
|
|
|
|
WLOG(WS_LOG_DEBUG, "UNIMPLEMENTED: seq %u", seq);
|
|
|
|
*idx = begin;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoDisconnect(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 reason;
|
|
const char* reasonStr;
|
|
word32 begin = *idx;
|
|
|
|
(void)ssh;
|
|
(void)len;
|
|
(void)reasonStr;
|
|
|
|
ato32(buf + begin, &reason);
|
|
begin += UINT32_SZ;
|
|
|
|
#ifdef NO_WOLFSSH_STRINGS
|
|
WLOG(WS_LOG_DEBUG, "DISCONNECT: (%u)", reason);
|
|
#elif defined(DEBUG_WOLFSSH)
|
|
switch (reason) {
|
|
case WOLFSSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT:
|
|
reasonStr = "host not allowed to connect"; break;
|
|
case WOLFSSH_DISCONNECT_PROTOCOL_ERROR:
|
|
reasonStr = "protocol error"; break;
|
|
case WOLFSSH_DISCONNECT_KEY_EXCHANGE_FAILED:
|
|
reasonStr = "key exchange failed"; break;
|
|
case WOLFSSH_DISCONNECT_RESERVED:
|
|
reasonStr = "reserved"; break;
|
|
case WOLFSSH_DISCONNECT_MAC_ERROR:
|
|
reasonStr = "mac error"; break;
|
|
case WOLFSSH_DISCONNECT_COMPRESSION_ERROR:
|
|
reasonStr = "compression error"; break;
|
|
case WOLFSSH_DISCONNECT_SERVICE_NOT_AVAILABLE:
|
|
reasonStr = "service not available"; break;
|
|
case WOLFSSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED:
|
|
reasonStr = "protocol version not supported"; break;
|
|
case WOLFSSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE:
|
|
reasonStr = "host key not verifiable"; break;
|
|
case WOLFSSH_DISCONNECT_CONNECTION_LOST:
|
|
reasonStr = "connection lost"; break;
|
|
case WOLFSSH_DISCONNECT_BY_APPLICATION:
|
|
reasonStr = "disconnect by application"; break;
|
|
case WOLFSSH_DISCONNECT_TOO_MANY_CONNECTIONS:
|
|
reasonStr = "too many connections"; break;
|
|
case WOLFSSH_DISCONNECT_AUTH_CANCELLED_BY_USER:
|
|
reasonStr = "auth cancelled by user"; break;
|
|
case WOLFSSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE:
|
|
reasonStr = "no more auth methods available"; break;
|
|
case WOLFSSH_DISCONNECT_ILLEGAL_USER_NAME:
|
|
reasonStr = "illegal user name"; break;
|
|
default:
|
|
reasonStr = "unknown reason";
|
|
}
|
|
WLOG(WS_LOG_DEBUG, "DISCONNECT: (%u) %s", reason, reasonStr);
|
|
#endif
|
|
|
|
*idx = begin;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoServiceRequest(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin = *idx;
|
|
word32 nameSz;
|
|
char serviceName[WOLFSSH_MAX_NAMESZ];
|
|
|
|
(void)len;
|
|
|
|
ato32(buf + begin, &nameSz);
|
|
begin += LENGTH_SZ;
|
|
|
|
if (begin + nameSz > len || nameSz >= WOLFSSH_MAX_NAMESZ) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
WMEMCPY(serviceName, buf + begin, nameSz);
|
|
begin += nameSz;
|
|
serviceName[nameSz] = 0;
|
|
|
|
*idx = begin;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Requesting service: %s", serviceName);
|
|
ssh->clientState = CLIENT_USERAUTH_REQUEST_DONE;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoServiceAccept(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin = *idx;
|
|
word32 nameSz;
|
|
char serviceName[WOLFSSH_MAX_NAMESZ];
|
|
|
|
(void)len;
|
|
|
|
ato32(buf + begin, &nameSz);
|
|
begin += LENGTH_SZ;
|
|
|
|
if (begin + nameSz > len || nameSz >= WOLFSSH_MAX_NAMESZ) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
WMEMCPY(serviceName, buf + begin, nameSz);
|
|
begin += nameSz;
|
|
serviceName[nameSz] = 0;
|
|
|
|
*idx = begin;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Accepted service: %s", serviceName);
|
|
ssh->serverState = SERVER_USERAUTH_REQUEST_DONE;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Utility for DoUserAuthRequest() */
|
|
static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
WS_UserAuthData_Password* pw;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestPassword()");
|
|
|
|
if (ssh == NULL || authData == NULL ||
|
|
buf == NULL || len == 0 || idx == NULL) {
|
|
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
pw = &authData->sf.password;
|
|
authData->type = WOLFSSH_USERAUTH_PASSWORD;
|
|
ret = GetBoolean(&pw->hasNewPassword, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&pw->passwordSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
pw->password = buf + begin;
|
|
begin += pw->passwordSz;
|
|
|
|
if (pw->hasNewPassword) {
|
|
/* Skip the password change. Maybe error out since we aren't
|
|
* supporting password changes at this time. */
|
|
ret = GetUint32(&pw->newPasswordSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
pw->newPassword = buf + begin;
|
|
begin += pw->newPasswordSz;
|
|
}
|
|
}
|
|
else {
|
|
pw->newPassword = NULL;
|
|
pw->newPasswordSz = 0;
|
|
}
|
|
|
|
if (ssh->ctx->userAuthCb != NULL) {
|
|
WLOG(WS_LOG_DEBUG, "DUARPW: Calling the userauth callback");
|
|
ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PASSWORD,
|
|
authData, ssh->userAuthCtx);
|
|
if (ret == WOLFSSH_USERAUTH_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DUARPW: password check successful");
|
|
ssh->clientState = CLIENT_USERAUTH_DONE;
|
|
ret = WS_SUCCESS;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "DUARPW: password check failed");
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
}
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "DUARPW: No user auth callback");
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
*idx = begin;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestPassword(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Utility for DoUserAuthRequestPublicKey() */
|
|
/* returns negative for error, positive is size of digest. */
|
|
static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk,
|
|
byte hashId, byte* digest, word32 digestSz)
|
|
{
|
|
RsaKey key;
|
|
byte checkDigest[MAX_ENCODED_SIG_SZ];
|
|
int checkDigestSz;
|
|
byte* publicKeyType;
|
|
word32 publicKeyTypeSz = 0;
|
|
byte* n;
|
|
word32 nSz = 0;
|
|
byte* e = NULL;
|
|
word32 eSz = 0;
|
|
word32 i = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestRsa()");
|
|
|
|
if (ssh == NULL || pk == NULL || digest == NULL || digestSz == 0)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
/* First check that the public key's type matches the one we are
|
|
* expecting. */
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
publicKeyType = pk->publicKey + i;
|
|
i += publicKeyTypeSz;
|
|
if (publicKeyTypeSz != pk->publicKeyTypeSz &&
|
|
WMEMCMP(publicKeyType, pk->publicKeyType, publicKeyTypeSz) != 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Public Key's type does not match public key type");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&eSz, pk->publicKey, pk->publicKeySz, &i);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
e = pk->publicKey + i;
|
|
i += eSz;
|
|
ret = GetUint32(&nSz, pk->publicKey, pk->publicKeySz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
n = pk->publicKey + i;
|
|
|
|
ret = wc_InitRsaKey(&key, ssh->ctx->heap);
|
|
if (ret == 0)
|
|
ret = wc_RsaPublicKeyDecodeRaw(n, nSz, e, eSz, &key);
|
|
if (ret != 0) {
|
|
WLOG(WS_LOG_DEBUG, "Could not decode public key");
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
i = 0;
|
|
/* First check that the signature's public key type matches the one
|
|
* we are expecting. */
|
|
ret = GetUint32(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
publicKeyType = pk->publicKey + i;
|
|
i += publicKeyTypeSz;
|
|
|
|
if (publicKeyTypeSz != pk->publicKeyTypeSz &&
|
|
WMEMCMP(publicKeyType, pk->publicKeyType, publicKeyTypeSz) != 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Signature's type does not match public key type");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&nSz, pk->signature, pk->signatureSz, &i);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
n = pk->signature + i;
|
|
checkDigestSz = wc_RsaSSL_Verify(n, nSz, checkDigest,
|
|
sizeof(checkDigest), &key);
|
|
if (checkDigestSz <= 0) {
|
|
WLOG(WS_LOG_DEBUG, "Could not verify signature");
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
byte encDigest[MAX_ENCODED_SIG_SZ];
|
|
word32 encDigestSz;
|
|
volatile int compare;
|
|
volatile int sizeCompare;
|
|
|
|
encDigestSz = wc_EncodeSignature(encDigest, digest,
|
|
wc_HashGetDigestSize((enum wc_HashType)hashId),
|
|
wc_HashGetOID((enum wc_HashType)hashId));
|
|
|
|
compare = ConstantCompare(encDigest, checkDigest,
|
|
encDigestSz);
|
|
sizeCompare = encDigestSz != (word32)checkDigestSz;
|
|
|
|
if ((compare | sizeCompare) == 0)
|
|
ret = WS_SUCCESS;
|
|
else
|
|
ret = WS_RSA_E;
|
|
}
|
|
|
|
wc_FreeRsaKey(&key);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestRsa(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Utility for DoUserAuthRequestPublicKey() */
|
|
/* returns negative for error, positive is size of digest. */
|
|
static int DoUserAuthRequestEcc(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk,
|
|
byte hashId, byte* digest, word32 digestSz)
|
|
{
|
|
ecc_key key;
|
|
byte* publicKeyType;
|
|
word32 publicKeyTypeSz = 0;
|
|
byte* curveName;
|
|
word32 curveNameSz = 0;
|
|
mp_int r, s;
|
|
byte* q = NULL;
|
|
word32 sz, qSz;
|
|
word32 i = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
(void)hashId;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestEcc()");
|
|
|
|
if (ssh == NULL || pk == NULL || digest == NULL || digestSz == 0)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
/* First check that the public key's type matches the one we are
|
|
* expecting. */
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
publicKeyType = pk->publicKey + i;
|
|
i += publicKeyTypeSz;
|
|
if (publicKeyTypeSz != pk->publicKeyTypeSz &&
|
|
WMEMCMP(publicKeyType, pk->publicKeyType, publicKeyTypeSz) != 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Public Key's type does not match public key type");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&curveNameSz, pk->publicKey, pk->publicKeySz, &i);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
curveName = pk->publicKey + i;
|
|
(void)curveName; /* Not used at the moment, hush the compiler. */
|
|
i += curveNameSz;
|
|
ret = GetUint32(&qSz, pk->publicKey, pk->publicKeySz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
q = pk->publicKey + i;
|
|
i += qSz;
|
|
ret = wc_ecc_init_ex(&key, ssh->ctx->heap, INVALID_DEVID);
|
|
}
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_import_x963(q, qSz, &key);
|
|
|
|
if (ret != 0) {
|
|
WLOG(WS_LOG_DEBUG, "Could not decode public key");
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
i = 0;
|
|
/* First check that the signature's public key type matches the one
|
|
* we are expecting. */
|
|
ret = GetUint32(&publicKeyTypeSz, pk->signature, pk->signatureSz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
publicKeyType = pk->signature + i;
|
|
i += publicKeyTypeSz;
|
|
|
|
if (publicKeyTypeSz != pk->publicKeyTypeSz &&
|
|
WMEMCMP(publicKeyType, pk->publicKeyType, publicKeyTypeSz) != 0) {
|
|
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Signature's type does not match public key type");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* Get the size of the signature blob. */
|
|
ret = GetUint32(&sz, pk->signature, pk->signatureSz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* Get R and S. */
|
|
ret = GetUint32(&sz, pk->signature, pk->signatureSz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = mp_read_unsigned_bin(&r, pk->signature + i, sz);
|
|
if (ret != 0)
|
|
ret = WS_PARSE_E;
|
|
else
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
i += sz;
|
|
ret = GetUint32(&sz, pk->signature, pk->signatureSz, &i);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = mp_read_unsigned_bin(&s, pk->signature + i, sz);
|
|
if (ret != 0)
|
|
ret = WS_PARSE_E;
|
|
else
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
int status = 0;
|
|
|
|
ret = wc_ecc_verify_hash_ex(&r, &s, digest, digestSz, &status, &key);
|
|
if (ret != 0) {
|
|
WLOG(WS_LOG_DEBUG, "Could not verify signature");
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
else
|
|
ret = status ? WS_SUCCESS : WS_ECC_E;
|
|
}
|
|
|
|
mp_clear(&r);
|
|
mp_clear(&s);
|
|
wc_ecc_free(&key);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestEcc(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Utility for DoUserAuthRequest() */
|
|
static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
WS_UserAuthData_PublicKey* pk;
|
|
int ret = WS_SUCCESS;
|
|
int authFailure = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestPublicKey()");
|
|
|
|
if (ssh == NULL || authData == NULL ||
|
|
buf == NULL || len == 0 || idx == NULL) {
|
|
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
pk = &authData->sf.publicKey;
|
|
authData->type = WOLFSSH_USERAUTH_PUBLICKEY;
|
|
ret = GetBoolean(&pk->hasSignature, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&pk->publicKeyTypeSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
pk->publicKeyType = buf + begin;
|
|
begin += pk->publicKeyTypeSz;
|
|
ret = GetUint32(&pk->publicKeySz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
pk->publicKey = buf + begin;
|
|
begin += pk->publicKeySz;
|
|
|
|
if (pk->hasSignature) {
|
|
ret = GetUint32(&pk->signatureSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS) {
|
|
pk->signature = buf + begin;
|
|
begin += pk->signatureSz;
|
|
}
|
|
}
|
|
else {
|
|
pk->signature = NULL;
|
|
pk->signatureSz = 0;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
|
|
if (ssh->ctx->userAuthCb != NULL) {
|
|
WLOG(WS_LOG_DEBUG, "DUARPK: Calling the userauth callback");
|
|
ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PUBLICKEY,
|
|
authData, ssh->userAuthCtx);
|
|
WLOG(WS_LOG_DEBUG, "DUARPK: callback result = %d", ret);
|
|
if (ret == WOLFSSH_USERAUTH_SUCCESS)
|
|
ret = WS_SUCCESS;
|
|
else {
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
authFailure = 1;
|
|
}
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "DUARPK: no userauth callback set");
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
authFailure = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS && !authFailure) {
|
|
if (pk->signature == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "DUARPK: Send the PK OK");
|
|
ret = SendUserAuthPkOk(ssh, pk->publicKeyType, pk->publicKeyTypeSz,
|
|
pk->publicKey, pk->publicKeySz);
|
|
}
|
|
else {
|
|
wc_HashAlg hash;
|
|
byte digest[WC_MAX_DIGEST_SIZE];
|
|
word32 digestSz;
|
|
byte hashId = WC_HASH_TYPE_SHA;
|
|
byte pkTypeId;
|
|
|
|
pkTypeId = NameToId((char*)pk->publicKeyType,
|
|
pk->publicKeyTypeSz);
|
|
if (pkTypeId == ID_UNKNOWN)
|
|
ret = WS_INVALID_ALGO_ID;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
hashId = HashForId(pkTypeId);
|
|
WMEMSET(digest, 0, sizeof(digest));
|
|
digestSz = wc_HashGetDigestSize((enum wc_HashType)hashId);
|
|
ret = wc_HashInit(&hash, (enum wc_HashType)hashId);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
c32toa(ssh->sessionIdSz, digest);
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, digest, UINT32_SZ);
|
|
}
|
|
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId,
|
|
ssh->sessionId, ssh->sessionIdSz);
|
|
|
|
if (ret == 0) {
|
|
digest[0] = MSGID_USERAUTH_REQUEST;
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, digest, MSG_ID_SZ);
|
|
}
|
|
|
|
/* The rest of the fields in the signature are already
|
|
* in the buffer. Just need to account for the sizes. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&hash, (enum wc_HashType)hashId, pk->dataToSign,
|
|
authData->usernameSz +
|
|
authData->serviceNameSz +
|
|
authData->authNameSz + BOOLEAN_SZ +
|
|
pk->publicKeyTypeSz + pk->publicKeySz +
|
|
(UINT32_SZ * 5));
|
|
if (ret == 0) {
|
|
ret = wc_HashFinal(&hash, (enum wc_HashType)hashId, digest);
|
|
|
|
if (ret != 0)
|
|
ret = WS_CRYPTO_FAILED;
|
|
else
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (pkTypeId == ID_SSH_RSA)
|
|
ret = DoUserAuthRequestRsa(ssh, pk,
|
|
hashId, digest, digestSz);
|
|
else if (pkTypeId == ID_ECDSA_SHA2_NISTP256 ||
|
|
pkTypeId == ID_ECDSA_SHA2_NISTP384 ||
|
|
pkTypeId == ID_ECDSA_SHA2_NISTP521)
|
|
ret = DoUserAuthRequestEcc(ssh, pk,
|
|
hashId, digest, digestSz);
|
|
}
|
|
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DUARPK: signature compare failure");
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
}
|
|
else {
|
|
ssh->clientState = CLIENT_USERAUTH_DONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestPublicKey(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoUserAuthRequest(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
byte authNameId;
|
|
WS_UserAuthData authData;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequest()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
WMEMSET(&authData, 0, sizeof(authData));
|
|
ret = GetUint32(&authData.usernameSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
authData.username = buf + begin;
|
|
begin += authData.usernameSz;
|
|
|
|
ret = GetUint32(&authData.serviceNameSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
authData.serviceName = buf + begin;
|
|
begin += authData.serviceNameSz;
|
|
|
|
ret = GetUint32(&authData.authNameSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
authData.authName = buf + begin;
|
|
begin += authData.authNameSz;
|
|
authNameId = NameToId((char*)authData.authName, authData.authNameSz);
|
|
|
|
if (authNameId == ID_USERAUTH_PASSWORD)
|
|
ret = DoUserAuthRequestPassword(ssh, &authData, buf, len, &begin);
|
|
else if (authNameId == ID_USERAUTH_PUBLICKEY) {
|
|
authData.sf.publicKey.dataToSign = buf + *idx;
|
|
ret = DoUserAuthRequestPublicKey(ssh, &authData, buf, len, &begin);
|
|
}
|
|
#ifdef WOLFSSH_ALLOW_USERAUTH_NONE
|
|
else if (authNameId == ID_NONE) {
|
|
ssh->clientState = CLIENT_USERAUTH_DONE;
|
|
}
|
|
#endif
|
|
else {
|
|
WLOG(WS_LOG_DEBUG,
|
|
"invalid userauth type: %s", IdToName(authNameId));
|
|
ret = SendUserAuthFailure(ssh, 0);
|
|
}
|
|
|
|
*idx = begin;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoUserAuthFailure(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
byte authList[3]; /* Should only ever be password, publickey, hostname */
|
|
word32 authListSz = 3;
|
|
byte partialSuccess;
|
|
byte authId = ID_USERAUTH_PASSWORD;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthFailure()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetNameList(authList, &authListSz, buf, len, idx);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetBoolean(&partialSuccess, buf, len, idx);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = SendUserAuthRequest(ssh, authId);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthFailure(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoUserAuthSuccess(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthSuccess()");
|
|
|
|
/* This message does not have any payload. len should be 0. */
|
|
if (ssh == NULL || buf == NULL || len != 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
ssh->serverState = SERVER_USERAUTH_ACCEPT_DONE;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthSuccess(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoUserAuthBanner(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
char banner[80];
|
|
word32 bannerSz = sizeof(banner);
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthBanner()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetString(banner, &bannerSz, buf, len, idx);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&bannerSz, buf, len, idx);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->ctx->showBanner) {
|
|
WLOG(WS_LOG_INFO, "%s", banner);
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthBanner(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoGlobalRequest(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
char name[80];
|
|
word32 nameSz = sizeof(name);
|
|
byte wantReply = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoGlobalRequest()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetString(name, &nameSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "DGR: request name = %s", name);
|
|
ret = GetBoolean(&wantReply, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx += len;
|
|
|
|
if (wantReply)
|
|
ret = SendRequestSuccess(ssh, 0);
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoGlobalRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_FWD
|
|
static int DoChannelOpenForward(WOLFSSH* ssh,
|
|
char** host, word32* hostPort,
|
|
char** origin, word32* originPort,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelOpenForward()");
|
|
|
|
if (idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetStringAlloc(ssh, host, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(hostPort, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetStringAlloc(ssh, origin, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(originPort, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
WLOG(WS_LOG_INFO, " host = %s:%u", *host, *hostPort);
|
|
WLOG(WS_LOG_INFO, " origin = %s:%u", *origin, *originPort);
|
|
}
|
|
else {
|
|
*idx += len;
|
|
WFREE(*host, ssh->ctx->heap, DYNTYPE_STRING);
|
|
WFREE(*origin, ssh->ctx->heap, DYNTYPE_STRING);
|
|
*host = NULL;
|
|
*origin = NULL;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelOpenForward(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
#endif /* WOLFSSH_FWD */
|
|
|
|
|
|
static int DoChannelOpen(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
word32 begin;
|
|
word32 typeSz;
|
|
char type[32];
|
|
byte typeId = ID_UNKNOWN;
|
|
word32 peerChannelId;
|
|
word32 peerInitialWindowSz;
|
|
word32 peerMaxPacketSz;
|
|
#ifdef WOLFSSH_FWD
|
|
char* host = NULL;
|
|
char* origin = NULL;
|
|
word32 hostPort, originPort;
|
|
#endif /* WOLFSSH_FWD */
|
|
WOLFSSH_CHANNEL* newChannel;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelOpen()");
|
|
|
|
if (idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
typeSz = sizeof(type);
|
|
ret = GetString(type, &typeSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerChannelId, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerInitialWindowSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerMaxPacketSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_INFO, " type = %s", type);
|
|
WLOG(WS_LOG_INFO, " peerChannelId = %u", peerChannelId);
|
|
WLOG(WS_LOG_INFO, " peerInitialWindowSz = %u", peerInitialWindowSz);
|
|
WLOG(WS_LOG_INFO, " peerMaxPacketSz = %u", peerMaxPacketSz);
|
|
|
|
typeId = NameToId(type, typeSz);
|
|
switch (typeId) {
|
|
case ID_CHANTYPE_SESSION:
|
|
break;
|
|
#ifdef WOLFSSH_FWD
|
|
/*case ID_CHANTYPE_TCPIP_FORWARD:*/
|
|
case ID_CHANTYPE_TCPIP_DIRECT:
|
|
ret = DoChannelOpenForward(ssh,
|
|
&host, &hostPort, &origin, &originPort,
|
|
buf, len, &begin);
|
|
break;
|
|
#endif /* WOLFSSH_FWD */
|
|
default:
|
|
ret = WS_INVALID_CHANTYPE;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
|
|
newChannel = ChannelNew(ssh, typeId,
|
|
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
|
|
if (newChannel == NULL)
|
|
ret = WS_RESOURCE_E;
|
|
else {
|
|
ChannelUpdatePeer(newChannel, peerChannelId,
|
|
peerInitialWindowSz, peerMaxPacketSz);
|
|
if (ssh->channelListSz == 0)
|
|
ssh->defaultPeerChannelId = peerChannelId;
|
|
#ifdef WOLFSSH_FWD
|
|
switch (typeId) {
|
|
/*case ID_CHANTYPE_TCPIP_FORWARD:*/
|
|
case ID_CHANTYPE_TCPIP_DIRECT:
|
|
|
|
ChannelUpdateForward(newChannel,
|
|
host, hostPort, origin, originPort);
|
|
break;
|
|
}
|
|
#endif /* WOLFSSH_FWD */
|
|
ChannelAppend(ssh, newChannel);
|
|
|
|
ssh->clientState = CLIENT_CHANNEL_OPEN_DONE;
|
|
}
|
|
}
|
|
|
|
#ifdef WOLFSSH_FWD
|
|
if (ret != WS_SUCCESS) {
|
|
WFREE(host, ssh->ctx->heap, DYNTYPE_STRING);
|
|
WFREE(origin, ssh->ctx->heap, DYNTYPE_STRING);
|
|
}
|
|
#endif /* WOLFSSH_FWD */
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelOpen(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelOpenConf(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel;
|
|
word32 begin, channelId, peerChannelId,
|
|
peerInitialWindowSz, peerMaxPacketSz;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelOpenConf()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerChannelId, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerInitialWindowSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&peerMaxPacketSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_INFO, " channelId = %u", channelId);
|
|
WLOG(WS_LOG_INFO, " peerChannelId = %u", peerChannelId);
|
|
WLOG(WS_LOG_INFO, " peerInitialWindowSz = %u", peerInitialWindowSz);
|
|
WLOG(WS_LOG_INFO, " peerMaxPacketSz = %u", peerMaxPacketSz);
|
|
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = ChannelUpdatePeer(channel, peerChannelId,
|
|
peerInitialWindowSz, peerMaxPacketSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->serverState = SERVER_CHANNEL_OPEN_DONE;
|
|
ssh->defaultPeerChannelId = peerChannelId;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelOpenConf(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelOpenFail(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
char desc[80];
|
|
word32 begin, channelId, reasonId, descSz, langSz;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelOpenFail()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
begin = *idx;
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&reasonId, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
descSz = sizeof(desc);
|
|
ret = GetString(desc, &descSz, buf, len, &begin);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&langSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin + langSz;
|
|
|
|
WLOG(WS_LOG_INFO, "channel open failure reason code: %u", reasonId);
|
|
if (descSz > 0) {
|
|
WLOG(WS_LOG_INFO, "description: %s", desc);
|
|
}
|
|
|
|
ret = ChannelRemove(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = WS_CHANOPEN_FAILED;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelOpenFail(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelEof(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 channelId;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelEof()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
channel->receivedEof = 1;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelEof(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelClose(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 channelId;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelClose()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = SendChannelClose(ssh, channel->peerChannel);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = ChannelRemove(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = WS_CHANNEL_CLOSED;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelClose(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelRequest(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 channelId;
|
|
word32 typeSz;
|
|
char type[32];
|
|
byte wantReply;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelRequest()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
|
|
typeSz = sizeof(type);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetString(type, &typeSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetBoolean(&wantReply, buf, len, &begin);
|
|
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, " channelId = %u", channelId);
|
|
WLOG(WS_LOG_DEBUG, " type = %s", type);
|
|
WLOG(WS_LOG_DEBUG, " wantReply = %u", wantReply);
|
|
|
|
#ifdef WOLFSSH_TERM
|
|
if (WSTRNCMP(type, "pty-req", typeSz) == 0) {
|
|
char term[32];
|
|
word32 termSz;
|
|
word32 widthChar, heightRows, widthPixels, heightPixels;
|
|
word32 modesSz;
|
|
|
|
termSz = sizeof(term);
|
|
ret = GetString(term, &termSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&widthChar, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&heightRows, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&widthPixels, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&heightPixels, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&modesSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, " term = %s", term);
|
|
WLOG(WS_LOG_DEBUG, " widthChar = %u", widthChar);
|
|
WLOG(WS_LOG_DEBUG, " heightRows = %u", heightRows);
|
|
WLOG(WS_LOG_DEBUG, " widthPixels = %u", widthPixels);
|
|
WLOG(WS_LOG_DEBUG, " heightPixels = %u", heightPixels);
|
|
WLOG(WS_LOG_DEBUG, " modes = %u", (modesSz - 1) / 5);
|
|
}
|
|
}
|
|
else
|
|
#endif /* WOLFSSH_TERM */
|
|
if (WSTRNCMP(type, "env", typeSz) == 0) {
|
|
char name[WOLFSSH_MAX_NAMESZ];
|
|
word32 nameSz;
|
|
char value[32];
|
|
word32 valueSz;
|
|
|
|
nameSz = sizeof(name);
|
|
valueSz = sizeof(value);
|
|
ret = GetString(name, &nameSz, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetString(value, &valueSz, buf, len, &begin);
|
|
|
|
WLOG(WS_LOG_DEBUG, " %s = %s", name, value);
|
|
}
|
|
else if (WSTRNCMP(type, "shell", typeSz) == 0) {
|
|
channel->sessionType = WOLFSSH_SESSION_SHELL;
|
|
ssh->clientState = CLIENT_DONE;
|
|
}
|
|
else if (WSTRNCMP(type, "exec", typeSz) == 0) {
|
|
ret = GetStringAlloc(ssh, &channel->command, buf, len, &begin);
|
|
channel->sessionType = WOLFSSH_SESSION_EXEC;
|
|
ssh->clientState = CLIENT_DONE;
|
|
|
|
WLOG(WS_LOG_DEBUG, " command = %s", channel->command);
|
|
}
|
|
else if (WSTRNCMP(type, "subsystem", typeSz) == 0) {
|
|
ret = GetStringAlloc(ssh, &channel->command, buf, len, &begin);
|
|
channel->sessionType = WOLFSSH_SESSION_SUBSYSTEM;
|
|
ssh->clientState = CLIENT_DONE;
|
|
|
|
WLOG(WS_LOG_DEBUG, " subsystem = %s", channel->command);
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
*idx = len;
|
|
|
|
if (wantReply) {
|
|
int replyRet;
|
|
|
|
replyRet = SendChannelSuccess(ssh, channelId, (ret == WS_SUCCESS));
|
|
if (replyRet != WS_SUCCESS)
|
|
ret = replyRet;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelSuccess(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelSuccess()");
|
|
|
|
if (ssh == NULL || buf == NULL || len == 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
ssh->serverState = SERVER_DONE;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelSuccess(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelFailure(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelFailure()");
|
|
|
|
if (ssh == NULL || buf == NULL || len != 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = WS_CHANOPEN_FAILED;
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelFailure(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelWindowAdjust(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 channelId, bytesToAdd;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelWindowAdjust()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&bytesToAdd, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin;
|
|
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
else {
|
|
WLOG(WS_LOG_INFO, " channelId = %u", channelId);
|
|
WLOG(WS_LOG_INFO, " bytesToAdd = %u", bytesToAdd);
|
|
WLOG(WS_LOG_INFO, " peerWindowSz = %u",
|
|
channel->peerWindowSz);
|
|
|
|
channel->peerWindowSz += bytesToAdd;
|
|
|
|
WLOG(WS_LOG_INFO, " update peerWindowSz = %u",
|
|
channel->peerWindowSz);
|
|
|
|
}
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelWindowAdjust(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoChannelData(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 dataSz = 0;
|
|
word32 channelId;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelData()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&dataSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*idx = begin + dataSz;
|
|
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
else
|
|
ret = ChannelPutData(channel, buf + begin, dataSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->lastRxId = channelId;
|
|
ret = WS_CHAN_RXD;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelData(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* deletes current buffer and updates it
|
|
* return WS_SUCCESS on success */
|
|
static int PutBuffer(Buffer* buf, byte* data, word32 dataSz)
|
|
{
|
|
int ret;
|
|
|
|
/* reset "used" section of buffer back to 0 */
|
|
buf->length = 0;
|
|
buf->idx = 0;
|
|
|
|
if (dataSz > buf->bufferSz) {
|
|
if ((ret = GrowBuffer(buf, dataSz, 0)) != WS_SUCCESS) {
|
|
return ret;
|
|
}
|
|
}
|
|
WMEMCPY(buf->buffer, data, dataSz);
|
|
buf->length = dataSz;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int DoChannelExtendedData(WOLFSSH* ssh,
|
|
byte* buf, word32 len, word32* idx)
|
|
{
|
|
WOLFSSH_CHANNEL* channel = NULL;
|
|
word32 begin = *idx;
|
|
word32 dataSz = 0;
|
|
word32 channelId;
|
|
word32 dataTypeCode;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering DoChannelExtendedData()");
|
|
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&dataTypeCode, buf, len, &begin);
|
|
if (ret == WS_SUCCESS)
|
|
ret = (dataTypeCode == CHANNEL_EXTENDED_DATA_STDERR) ?
|
|
WS_SUCCESS : WS_INVALID_EXTDATA;
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetUint32(&dataSz, buf, len, &begin);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
else {
|
|
switch (dataTypeCode) {
|
|
case CHANNEL_EXTENDED_DATA_STDERR:
|
|
ret = PutBuffer(&ssh->extDataBuffer, buf + begin, dataSz);
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_ERROR, "Unsupported extended data type");
|
|
}
|
|
#ifdef DEBUG_WOLFSSH
|
|
DumpOctetString(buf + begin, dataSz);
|
|
#endif
|
|
if (ret == WS_SUCCESS) {
|
|
ret = SendChannelWindowAdjust(ssh, channel->peerChannel,dataSz);
|
|
}
|
|
}
|
|
*idx = begin + dataSz;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->lastRxId = channelId;
|
|
ret = WS_EXTDATA;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving DoChannelExtendedData(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int DoPacket(WOLFSSH* ssh)
|
|
{
|
|
byte* buf = (byte*)ssh->inputBuffer.buffer;
|
|
word32 idx = ssh->inputBuffer.idx;
|
|
word32 len = ssh->inputBuffer.length;
|
|
word32 payloadSz;
|
|
byte padSz;
|
|
byte msg;
|
|
word32 payloadIdx = 0;
|
|
int ret;
|
|
|
|
WLOG(WS_LOG_DEBUG, "DoPacket sequence number: %d", ssh->peerSeq);
|
|
|
|
idx += LENGTH_SZ;
|
|
padSz = buf[idx++];
|
|
|
|
/* check for underflow */
|
|
if ((word32)(PAD_LENGTH_SZ + padSz + MSG_ID_SZ) > ssh->curSz) {
|
|
return WS_OVERFLOW_E;
|
|
}
|
|
|
|
payloadSz = ssh->curSz - PAD_LENGTH_SZ - padSz - MSG_ID_SZ;
|
|
|
|
msg = buf[idx++];
|
|
/* At this point, payload starts at "buf + idx". */
|
|
|
|
/* sanity check on payloadSz. Uses "or" condition because of the case when
|
|
* adding idx to payloadSz causes it to overflow.
|
|
*/
|
|
if ((ssh->inputBuffer.bufferSz < payloadSz + idx) ||
|
|
(payloadSz + idx < payloadSz)) {
|
|
return WS_OVERFLOW_E;
|
|
}
|
|
|
|
switch (msg) {
|
|
|
|
case MSGID_DISCONNECT:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_DISCONNECT");
|
|
ret = DoDisconnect(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_IGNORE:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_IGNORE");
|
|
ret = DoIgnore(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_UNIMPLEMENTED:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_UNIMPLEMENTED");
|
|
ret = DoUnimplemented(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_DEBUG:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_DEBUG");
|
|
ret = DoDebug(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_KEXINIT:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXINIT");
|
|
ret = DoKexInit(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_NEWKEYS:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_NEWKEYS");
|
|
ret = DoNewKeys(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_KEXDH_INIT:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
|
|
ret = DoKexDhInit(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_KEXDH_REPLY:
|
|
if (ssh->handshake == NULL) {
|
|
ret = WS_MEMORY_E;
|
|
break;
|
|
}
|
|
|
|
if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_GEX_GROUP");
|
|
ret = DoKexDhGexGroup(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_REPLY");
|
|
ret = DoKexDhReply(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
}
|
|
break;
|
|
|
|
case MSGID_KEXDH_GEX_REQUEST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_GEX_REQUEST");
|
|
ret = DoKexDhGexRequest(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_KEXDH_GEX_INIT:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_GEX_INIT");
|
|
ret = DoKexDhInit(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_KEXDH_GEX_REPLY:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_GEX_INIT");
|
|
ret = DoKexDhReply(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_SERVICE_REQUEST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_SERVICE_REQUEST");
|
|
ret = DoServiceRequest(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_SERVICE_ACCEPT:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_SERVER_ACCEPT");
|
|
ret = DoServiceAccept(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_USERAUTH_REQUEST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_REQUEST");
|
|
ret = DoUserAuthRequest(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_USERAUTH_FAILURE:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_FAILURE");
|
|
ret = DoUserAuthFailure(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_USERAUTH_SUCCESS:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_SUCCESS");
|
|
ret = DoUserAuthSuccess(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_USERAUTH_BANNER:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_BANNER");
|
|
ret = DoUserAuthBanner(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_GLOBAL_REQUEST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_GLOBAL_REQUEST");
|
|
ret = DoGlobalRequest(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_OPEN:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_OPEN");
|
|
ret = DoChannelOpen(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_OPEN_CONF:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_OPEN_CONF");
|
|
ret = DoChannelOpenConf(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_OPEN_FAIL:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_OPEN_FAIL");
|
|
ret = DoChannelOpenFail(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_WINDOW_ADJUST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_WINDOW_ADJUST");
|
|
ret = DoChannelWindowAdjust(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_DATA:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_DATA");
|
|
ret = DoChannelData(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_EXTENDED_DATA:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_EXTENDED_DATA");
|
|
ret = DoChannelExtendedData(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_EOF:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_EOF");
|
|
ret = DoChannelEof(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_CLOSE:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_CLOSE");
|
|
ret = DoChannelClose(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_REQUEST:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_REQUEST");
|
|
ret = DoChannelRequest(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_SUCCESS:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_SUCCESS");
|
|
ret = DoChannelSuccess(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
case MSGID_CHANNEL_FAILURE:
|
|
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_FAILURE");
|
|
ret = DoChannelFailure(ssh, buf + idx, payloadSz, &payloadIdx);
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "Unimplemented message ID (%d)", msg);
|
|
#ifdef SHOW_UNIMPLEMENTED
|
|
DumpOctetString(buf + idx, payloadSz);
|
|
#endif
|
|
ret = SendUnimplemented(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS || ret == WS_CHAN_RXD || ret == WS_EXTDATA) {
|
|
idx += payloadIdx;
|
|
|
|
if (idx + padSz > len) {
|
|
WLOG(WS_LOG_DEBUG, "Not enough data in buffer for pad.");
|
|
ret = WS_BUFFER_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS || ret == WS_CHAN_RXD || ret == WS_EXTDATA) {
|
|
idx += padSz;
|
|
ssh->inputBuffer.idx = idx;
|
|
ssh->peerSeq++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE int Encrypt(WOLFSSH* ssh, byte* cipher, const byte* input,
|
|
word16 sz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || cipher == NULL || input == NULL || sz == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Encrypt %s", IdToName(ssh->encryptId));
|
|
|
|
switch (ssh->encryptId) {
|
|
case ID_NONE:
|
|
break;
|
|
|
|
#ifdef HAVE_AES_CBC
|
|
case ID_AES128_CBC:
|
|
if (sz % AES_BLOCK_SIZE || wc_AesCbcEncrypt(&ssh->encryptCipher.aes,
|
|
cipher, input, sz) < 0) {
|
|
|
|
ret = WS_ENCRYPT_E;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_AES_COUNTER
|
|
case ID_AES128_CTR:
|
|
if (sz % AES_BLOCK_SIZE || wc_AesCtrEncrypt(&ssh->encryptCipher.aes,
|
|
cipher, input, sz) < 0) {
|
|
|
|
ret = WS_ENCRYPT_E;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
ssh->txCount += sz;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE int Decrypt(WOLFSSH* ssh, byte* plain, const byte* input,
|
|
word16 sz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || plain == NULL || input == NULL || sz == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Decrypt %s", IdToName(ssh->peerEncryptId));
|
|
|
|
switch (ssh->peerEncryptId) {
|
|
case ID_NONE:
|
|
break;
|
|
|
|
#ifdef HAVE_AES_CBC
|
|
case ID_AES128_CBC:
|
|
if (sz % AES_BLOCK_SIZE || wc_AesCbcDecrypt(&ssh->decryptCipher.aes,
|
|
plain, input, sz) < 0) {
|
|
|
|
ret = WS_DECRYPT_E;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_AES_COUNTER
|
|
case ID_AES128_CTR:
|
|
if (sz % AES_BLOCK_SIZE || wc_AesCtrEncrypt(&ssh->decryptCipher.aes,
|
|
plain, input, sz) < 0) {
|
|
|
|
ret = WS_DECRYPT_E;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
ssh->rxCount += sz;
|
|
HighwaterCheck(ssh, WOLFSSH_HWSIDE_RECEIVE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE int CreateMac(WOLFSSH* ssh, const byte* in, word32 inSz,
|
|
byte* mac)
|
|
{
|
|
byte flatSeq[LENGTH_SZ];
|
|
int ret;
|
|
|
|
c32toa(ssh->seq, flatSeq);
|
|
|
|
WLOG(WS_LOG_DEBUG, "CreateMac %s", IdToName(ssh->macId));
|
|
|
|
/* Need to MAC the sequence number and the unencrypted packet */
|
|
switch (ssh->macId) {
|
|
case ID_NONE:
|
|
ret = WS_SUCCESS;
|
|
break;
|
|
|
|
case ID_HMAC_SHA1_96:
|
|
{
|
|
Hmac hmac;
|
|
byte digest[WC_SHA_DIGEST_SIZE];
|
|
|
|
ret = wc_HmacSetKey(&hmac, WC_SHA,
|
|
ssh->keys.macKey, ssh->keys.macKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, in, inSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacFinal(&hmac, digest);
|
|
if (ret == WS_SUCCESS)
|
|
WMEMCPY(mac, digest, SHA1_96_SZ);
|
|
}
|
|
break;
|
|
|
|
case ID_HMAC_SHA1:
|
|
{
|
|
Hmac hmac;
|
|
|
|
ret = wc_HmacSetKey(&hmac, WC_SHA,
|
|
ssh->keys.macKey, ssh->keys.macKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, in, inSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacFinal(&hmac, mac);
|
|
}
|
|
break;
|
|
|
|
case ID_HMAC_SHA2_256:
|
|
{
|
|
Hmac hmac;
|
|
|
|
ret = wc_HmacSetKey(&hmac, WC_SHA256,
|
|
ssh->keys.macKey,
|
|
ssh->keys.macKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, in, inSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacFinal(&hmac, mac);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "Invalid Mac ID");
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE int VerifyMac(WOLFSSH* ssh, const byte* in, word32 inSz,
|
|
const byte* mac)
|
|
{
|
|
int ret;
|
|
byte flatSeq[LENGTH_SZ];
|
|
byte checkMac[MAX_HMAC_SZ];
|
|
Hmac hmac;
|
|
|
|
c32toa(ssh->peerSeq, flatSeq);
|
|
|
|
WLOG(WS_LOG_DEBUG, "VerifyMac %s", IdToName(ssh->peerMacId));
|
|
WLOG(WS_LOG_DEBUG, "VM: inSz = %u", inSz);
|
|
WLOG(WS_LOG_DEBUG, "VM: seq = %u", ssh->peerSeq);
|
|
WLOG(WS_LOG_DEBUG, "VM: keyLen = %u", ssh->peerKeys.macKeySz);
|
|
|
|
WMEMSET(checkMac, 0, sizeof(checkMac));
|
|
|
|
switch (ssh->peerMacId) {
|
|
case ID_NONE:
|
|
ret = WS_SUCCESS;
|
|
break;
|
|
|
|
case ID_HMAC_SHA1:
|
|
case ID_HMAC_SHA1_96:
|
|
ret = wc_HmacSetKey(&hmac, WC_SHA,
|
|
ssh->peerKeys.macKey, ssh->peerKeys.macKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, in, inSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacFinal(&hmac, checkMac);
|
|
if (ConstantCompare(checkMac, mac, ssh->peerMacSz) != 0)
|
|
ret = WS_VERIFY_MAC_E;
|
|
break;
|
|
|
|
case ID_HMAC_SHA2_256:
|
|
ret = wc_HmacSetKey(&hmac, WC_SHA256,
|
|
ssh->peerKeys.macKey, ssh->peerKeys.macKeySz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacUpdate(&hmac, in, inSz);
|
|
if (ret == WS_SUCCESS)
|
|
ret = wc_HmacFinal(&hmac, checkMac);
|
|
if (ConstantCompare(checkMac, mac, ssh->peerMacSz) != 0)
|
|
ret = WS_VERIFY_MAC_E;
|
|
break;
|
|
|
|
default:
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE void AeadIncrementExpIv(byte* iv)
|
|
{
|
|
int i;
|
|
|
|
iv += AEAD_IMP_IV_SZ;
|
|
|
|
for (i = AEAD_EXP_IV_SZ-1; i >= 0; i--) {
|
|
if (++iv[i]) return;
|
|
}
|
|
}
|
|
|
|
|
|
static INLINE int EncryptAead(WOLFSSH* ssh, byte* cipher,
|
|
const byte* input, word16 sz,
|
|
byte* authTag, const byte* auth,
|
|
word16 authSz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || cipher == NULL || input == NULL || sz == 0 ||
|
|
authTag == NULL || auth == NULL || authSz == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WLOG(WS_LOG_DEBUG, "EncryptAead %s", IdToName(ssh->encryptId));
|
|
|
|
#ifdef HAVE_AESGCM
|
|
if (ssh->encryptId == ID_AES128_GCM) {
|
|
ret = wc_AesGcmEncrypt(&ssh->encryptCipher.aes, cipher, input, sz,
|
|
ssh->keys.iv, ssh->keys.ivSz,
|
|
authTag, ssh->macSz, auth, authSz);
|
|
}
|
|
else
|
|
#endif
|
|
ret = WS_INVALID_ALGO_ID;
|
|
|
|
AeadIncrementExpIv(ssh->keys.iv);
|
|
ssh->txCount += sz;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE int DecryptAead(WOLFSSH* ssh, byte* plain,
|
|
const byte* input, word16 sz,
|
|
const byte* authTag, const byte* auth,
|
|
word16 authSz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || plain == NULL || input == NULL || sz == 0 ||
|
|
authTag == NULL || auth == NULL || authSz == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WLOG(WS_LOG_DEBUG, "DecryptAead %s", IdToName(ssh->peerEncryptId));
|
|
|
|
#ifdef HAVE_AESGCM
|
|
if (ssh->peerEncryptId == ID_AES128_GCM) {
|
|
ret = wc_AesGcmDecrypt(&ssh->decryptCipher.aes, plain, input, sz,
|
|
ssh->peerKeys.iv, ssh->peerKeys.ivSz,
|
|
authTag, ssh->peerMacSz, auth, authSz);
|
|
}
|
|
else
|
|
#endif
|
|
ret = WS_INVALID_ALGO_ID;
|
|
|
|
AeadIncrementExpIv(ssh->peerKeys.iv);
|
|
ssh->rxCount += sz;
|
|
HighwaterCheck(ssh, WOLFSSH_HWSIDE_RECEIVE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int DoReceive(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
int verifyResult;
|
|
word32 readSz;
|
|
byte peerBlockSz = ssh->peerBlockSz;
|
|
byte peerMacSz = ssh->peerMacSz;
|
|
byte aeadMode = ssh->peerAeadMode;
|
|
|
|
for (;;) {
|
|
switch (ssh->processReplyState) {
|
|
case PROCESS_INIT:
|
|
readSz = peerBlockSz;
|
|
WLOG(WS_LOG_DEBUG, "PR1: size = %u", readSz);
|
|
if ((ret = GetInputData(ssh, readSz)) < 0) {
|
|
return ret;
|
|
}
|
|
ssh->processReplyState = PROCESS_PACKET_LENGTH;
|
|
|
|
if (!aeadMode) {
|
|
/* Decrypt first block if encrypted */
|
|
ret = Decrypt(ssh,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx,
|
|
readSz);
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "PR: First decrypt fail");
|
|
ssh->error = ret;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
}
|
|
FALL_THROUGH;
|
|
/* no break */
|
|
|
|
case PROCESS_PACKET_LENGTH:
|
|
if (ssh->inputBuffer.idx + UINT32_SZ >
|
|
ssh->inputBuffer.bufferSz) {
|
|
ssh->error = WS_OVERFLOW_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
/* Peek at the packet_length field. */
|
|
ato32(ssh->inputBuffer.buffer + ssh->inputBuffer.idx,
|
|
&ssh->curSz);
|
|
if (ssh->curSz >
|
|
MAX_PACKET_SZ - (word32)peerMacSz - LENGTH_SZ) {
|
|
ssh->error = WS_OVERFLOW_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
ssh->processReplyState = PROCESS_PACKET_FINISH;
|
|
FALL_THROUGH;
|
|
/* no break */
|
|
|
|
case PROCESS_PACKET_FINISH:
|
|
readSz = ssh->curSz + LENGTH_SZ + peerMacSz;
|
|
WLOG(WS_LOG_DEBUG, "PR2: size = %u", readSz);
|
|
if (readSz > 0) {
|
|
if ((ret = GetInputData(ssh, readSz)) < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!aeadMode) {
|
|
if (ssh->curSz + LENGTH_SZ - peerBlockSz > 0) {
|
|
ret = Decrypt(ssh,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx + peerBlockSz,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx + peerBlockSz,
|
|
ssh->curSz + LENGTH_SZ - peerBlockSz);
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_INFO,
|
|
"Not trying to decrypt short message.");
|
|
}
|
|
|
|
/* Verify the buffer is big enough for the data and mac.
|
|
* Even if the decrypt step fails, verify the MAC anyway.
|
|
* This keeps consistent timing. */
|
|
verifyResult = VerifyMac(ssh,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx,
|
|
ssh->curSz + LENGTH_SZ,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx +
|
|
LENGTH_SZ + ssh->curSz);
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "PR: Decrypt fail");
|
|
ssh->error = ret;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
if (verifyResult != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "PR: VerifyMac fail");
|
|
ssh->error = verifyResult;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
ret = DecryptAead(ssh,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx +
|
|
LENGTH_SZ,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx +
|
|
LENGTH_SZ,
|
|
ssh->curSz,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx +
|
|
ssh->curSz + LENGTH_SZ,
|
|
ssh->inputBuffer.buffer +
|
|
ssh->inputBuffer.idx,
|
|
LENGTH_SZ);
|
|
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "PR: DecryptAead fail");
|
|
ssh->error = ret;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
ssh->processReplyState = PROCESS_PACKET;
|
|
FALL_THROUGH;
|
|
/* no break */
|
|
|
|
case PROCESS_PACKET:
|
|
ret = DoPacket(ssh);
|
|
if (ret < 0 && ret != WS_CHAN_RXD) {
|
|
ssh->error = ret;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
WLOG(WS_LOG_DEBUG, "PR3: peerMacSz = %u", ssh->peerMacSz);
|
|
ssh->inputBuffer.idx += ssh->peerMacSz;
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "Bad process input state, program error");
|
|
ssh->error = WS_INPUT_CASE_E;
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
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);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
|
|
int DoProtoId(WOLFSSH* ssh)
|
|
{
|
|
int ret;
|
|
word32 idSz;
|
|
byte* eol;
|
|
|
|
if ( (ret = GetInputText(ssh, &eol)) < 0) {
|
|
WLOG(WS_LOG_DEBUG, "get input text failed");
|
|
return ret;
|
|
}
|
|
|
|
if (eol == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "invalid EOL");
|
|
return WS_VERSION_E;
|
|
}
|
|
|
|
if (WSTRNCASECMP((char*)ssh->inputBuffer.buffer,
|
|
sshProtoIdStr, SSH_PROTO_SZ) == 0) {
|
|
|
|
if (ssh->ctx->side == WOLFSSH_ENDPOINT_SERVER)
|
|
ssh->clientState = CLIENT_VERSION_DONE;
|
|
else
|
|
ssh->serverState = SERVER_VERSION_DONE;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "SSH version mismatch");
|
|
return WS_VERSION_E;
|
|
}
|
|
if (WSTRNCMP((char*)ssh->inputBuffer.buffer,
|
|
OpenSSH, sizeof(OpenSSH)-1) == 0) {
|
|
ssh->clientOpenSSH = 1;
|
|
}
|
|
|
|
*eol = 0;
|
|
|
|
idSz = (word32)WSTRLEN((char*)ssh->inputBuffer.buffer);
|
|
|
|
/* Store the proto ID for later use. It is used in keying and rekeying. */
|
|
ssh->peerProtoId = (byte*)WMALLOC(idSz + LENGTH_SZ,
|
|
ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (ssh->peerProtoId == NULL)
|
|
ret = WS_MEMORY_E;
|
|
else {
|
|
c32toa(idSz, ssh->peerProtoId);
|
|
WMEMCPY(ssh->peerProtoId + LENGTH_SZ, ssh->inputBuffer.buffer, idSz);
|
|
ssh->peerProtoIdSz = idSz + LENGTH_SZ;
|
|
}
|
|
|
|
ssh->inputBuffer.idx += idSz + SSH_PROTO_EOL_SZ;
|
|
ShrinkBuffer(&ssh->inputBuffer, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendProtoId(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 sshProtoIdStrSz;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "%s", sshProtoIdStr);
|
|
sshProtoIdStrSz = (word32)WSTRLEN(sshProtoIdStr);
|
|
ret = GrowBuffer(&ssh->outputBuffer, sshProtoIdStrSz, 0);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WMEMCPY(ssh->outputBuffer.buffer, sshProtoIdStr, sshProtoIdStrSz);
|
|
ssh->outputBuffer.length = sshProtoIdStrSz;
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int PreparePacket(WOLFSSH* ssh, word32 payloadSz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
byte* output;
|
|
word32 outputSz;
|
|
word32 packetSz;
|
|
word32 usedSz;
|
|
byte paddingSz;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* Minimum value for paddingSz is 4. */
|
|
paddingSz = ssh->blockSz -
|
|
((ssh->aeadMode ? 0 : LENGTH_SZ) +
|
|
PAD_LENGTH_SZ + payloadSz) % ssh->blockSz;
|
|
if (paddingSz < MIN_PAD_LENGTH)
|
|
paddingSz += ssh->blockSz;
|
|
ssh->paddingSz = paddingSz;
|
|
packetSz = PAD_LENGTH_SZ + payloadSz + paddingSz;
|
|
outputSz = LENGTH_SZ + packetSz + ssh->macSz;
|
|
usedSz = ssh->outputBuffer.length - ssh->outputBuffer.idx;
|
|
|
|
ret = GrowBuffer(&ssh->outputBuffer, outputSz, usedSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->packetStartIdx = ssh->outputBuffer.length;
|
|
output = ssh->outputBuffer.buffer + ssh->outputBuffer.length;
|
|
|
|
/* fill in the packetSz, paddingSz */
|
|
c32toa(packetSz, output);
|
|
output[LENGTH_SZ] = paddingSz;
|
|
|
|
ssh->outputBuffer.length += LENGTH_SZ + PAD_LENGTH_SZ;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int BundlePacket(WOLFSSH* ssh)
|
|
{
|
|
byte* output = NULL;
|
|
word32 idx = 0;
|
|
byte paddingSz = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
paddingSz = ssh->paddingSz;
|
|
|
|
/* Add the padding */
|
|
WLOG(WS_LOG_DEBUG, "BP: paddingSz = %u", paddingSz);
|
|
if (ssh->encryptId == ID_NONE)
|
|
WMEMSET(output + idx, 0, paddingSz);
|
|
else if (wc_RNG_GenerateBlock(ssh->rng, output + idx, paddingSz) < 0)
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
|
|
if (!ssh->aeadMode) {
|
|
if (ret == WS_SUCCESS) {
|
|
idx += paddingSz;
|
|
|
|
ret = CreateMac(ssh, ssh->outputBuffer.buffer + ssh->packetStartIdx,
|
|
ssh->outputBuffer.length -
|
|
ssh->packetStartIdx + paddingSz,
|
|
output + idx);
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "BP: failed to add padding");
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
idx += ssh->macSz;
|
|
ret = Encrypt(ssh,
|
|
ssh->outputBuffer.buffer + ssh->packetStartIdx,
|
|
ssh->outputBuffer.buffer + ssh->packetStartIdx,
|
|
ssh->outputBuffer.length -
|
|
ssh->packetStartIdx + paddingSz);
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "BP: failed to generate mac");
|
|
}
|
|
}
|
|
else {
|
|
if (ret == WS_SUCCESS) {
|
|
idx += paddingSz;
|
|
ret = EncryptAead(ssh,
|
|
ssh->outputBuffer.buffer +
|
|
ssh->packetStartIdx + LENGTH_SZ,
|
|
ssh->outputBuffer.buffer +
|
|
ssh->packetStartIdx + LENGTH_SZ,
|
|
ssh->outputBuffer.length -
|
|
ssh->packetStartIdx + paddingSz -
|
|
LENGTH_SZ,
|
|
output + idx,
|
|
ssh->outputBuffer.buffer +
|
|
ssh->packetStartIdx,
|
|
LENGTH_SZ);
|
|
idx += ssh->macSz;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->seq++;
|
|
ssh->outputBuffer.length = idx;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_DEBUG, "BP: failed to encrypt buffer");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE void CopyNameList(byte* buf, word32* idx,
|
|
const char* src, word32 srcSz)
|
|
{
|
|
word32 begin = *idx;
|
|
|
|
c32toa(srcSz, buf + begin);
|
|
begin += LENGTH_SZ;
|
|
WMEMCPY(buf + begin, src, srcSz);
|
|
begin += srcSz;
|
|
|
|
*idx = begin;
|
|
}
|
|
|
|
static const char cannedEncAlgoNames[] =
|
|
#if defined(HAVE_AESGCM)
|
|
"aes128-gcm@openssh.com"
|
|
#endif
|
|
#if defined(HAVE_AESGCM) && defined(WOLFSSL_AES_COUNTER)
|
|
","
|
|
#endif
|
|
#if defined(WOLFSSL_AES_COUNTER)
|
|
"aes128-ctr"
|
|
#endif
|
|
#if (defined(HAVE_AESGCM) || defined(WOLFSSL_AES_COUNTER))\
|
|
&& defined(HAVE_AES_CBC)
|
|
","
|
|
#endif
|
|
#if defined(HAVE_AES_CBC)
|
|
"aes128-cbc"
|
|
#endif
|
|
;
|
|
#if !defined(HAVE_AESGCM) && !defined(WOLFSSL_AES_COUNTER)\
|
|
&& !defined(HAVE_AES_CBC)
|
|
#warning "You need at least one of AES-GCM, AES-CTR or AES-CBC."
|
|
#endif
|
|
|
|
static const char cannedMacAlgoNames[] =
|
|
#if !defined(WOLFSSH_NO_HMAC_SHA2_256)
|
|
"hmac-sha2-256"
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_HMAC_SHA2_256) && !defined(WOLFSSH_NO_HMAC_SHA1_96)
|
|
","
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_HMAC_SHA1_96)
|
|
"hmac-sha1-96"
|
|
#endif
|
|
#if (!defined(WOLFSSH_NO_HMAC_SHA2_256) || !defined(WOLFSSH_NO_HMAC_SHA1_96))\
|
|
&& !defined(WOLFSSH_NO_HMAC_SHA1)
|
|
","
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_HMAC_SHA1)
|
|
"hmac-sha1"
|
|
#endif
|
|
;
|
|
#if defined(WOLFSSH_NO_HMAC_SHA2_256) && defined(WOLFSSH_NO_HMAC_SHA1_96)\
|
|
&& defined(WOLFSSH_NO_HMAC_SHA1)
|
|
#warning "You need at least one of HMAC-SHA2-256, HMAC-SHA1-96 or HMAC-SHA1"
|
|
#endif
|
|
|
|
static const char cannedKeyAlgoRsaNames[] = "ssh-rsa";
|
|
static const char cannedKeyAlgoEcc256Names[] = "ecdsa-sha2-nistp256";
|
|
static const char cannedKeyAlgoEcc384Names[] = "ecdsa-sha2-nistp384";
|
|
static const char cannedKeyAlgoEcc521Names[] = "ecdsa-sha2-nistp521";
|
|
static const char cannedKexAlgoNames[] =
|
|
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256)
|
|
"ecdh-sha2-nistp256"
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_ECDH_GEX_SHA256)
|
|
","
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_ECDH_GEX_SHA256)
|
|
"diffie-hellman-group-exchange-sha256"
|
|
#endif
|
|
#if (!defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) || !defined(WOLFSSH_NO_ECDH_GEX_SHA256))\
|
|
&& !defined(WOLFSSH_NO_ECDH_GROUP14_SHA1)
|
|
","
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_ECDH_GROUP14_SHA1)
|
|
"diffie-hellman-group14-sha1"
|
|
#endif
|
|
#if (!defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) || !defined(WOLFSSH_NO_ECDH_GEX_SHA256) \
|
|
|| !defined(WOLFSSH_NO_ECDH_GROUP14_SHA1)) && !defined(WOLFSSH_NO_ECDH_GROUP1_SHA1)
|
|
","
|
|
#endif
|
|
#if !defined(WOLFSSH_NO_ECDH_GROUP1_SHA1)
|
|
"diffie-hellman-group1-sha1";
|
|
#endif
|
|
#if defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && defined(WOLFSSH_NO_ECDH_GEX_SHA256)\
|
|
&& defined(WOLFSSH_NO_ECDH_GROUP14_SHA1) && defined(WOLFSSH_NO_ECDH_GROUP1_SHA1)
|
|
#warning "You need at least one of ECDH-SHA2-NISTP256, ECDH-GEX-SHA256, "
|
|
"ECDH_GROUP14-SHA1 or ECDH-GROUP1-SHA1"
|
|
#endif
|
|
|
|
static const char cannedNoneNames[] = "none";
|
|
|
|
static const word32 cannedEncAlgoNamesSz = sizeof(cannedEncAlgoNames) - 1;
|
|
static const word32 cannedMacAlgoNamesSz = sizeof(cannedMacAlgoNames) - 1;
|
|
static const word32 cannedKeyAlgoRsaNamesSz = sizeof(cannedKeyAlgoRsaNames) - 1;
|
|
static const word32 cannedKeyAlgoEcc256NamesSz =
|
|
sizeof(cannedKeyAlgoEcc256Names) - 1;
|
|
static const word32 cannedKeyAlgoEcc384NamesSz =
|
|
sizeof(cannedKeyAlgoEcc384Names) - 1;
|
|
static const word32 cannedKeyAlgoEcc521NamesSz =
|
|
sizeof(cannedKeyAlgoEcc521Names) - 1;
|
|
static const word32 cannedKexAlgoNamesSz = sizeof(cannedKexAlgoNames) - 1;
|
|
static const word32 cannedNoneNamesSz = sizeof(cannedNoneNames) - 1;
|
|
|
|
|
|
int SendKexInit(WOLFSSH* ssh)
|
|
{
|
|
byte* output = NULL;
|
|
byte* payload = NULL;
|
|
word32 idx = 0;
|
|
word32 payloadSz = 0;
|
|
int ret = WS_SUCCESS;
|
|
const char* cannedKeyAlgoNames = NULL;
|
|
word32 cannedKeyAlgoNamesSz = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendKexInit()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->isKeying = 1;
|
|
if (ssh->handshake == NULL) {
|
|
ssh->handshake = HandshakeInfoNew(ssh->ctx->heap);
|
|
if (ssh->handshake == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Couldn't allocate handshake info");
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
switch (ssh->ctx->useEcc) {
|
|
case ECC_SECP256R1:
|
|
cannedKeyAlgoNames = cannedKeyAlgoEcc256Names;
|
|
cannedKeyAlgoNamesSz = cannedKeyAlgoEcc256NamesSz;
|
|
break;
|
|
case ECC_SECP384R1:
|
|
cannedKeyAlgoNames = cannedKeyAlgoEcc384Names;
|
|
cannedKeyAlgoNamesSz = cannedKeyAlgoEcc384NamesSz;
|
|
break;
|
|
case ECC_SECP521R1:
|
|
cannedKeyAlgoNames = cannedKeyAlgoEcc521Names;
|
|
cannedKeyAlgoNamesSz = cannedKeyAlgoEcc521NamesSz;
|
|
break;
|
|
default:
|
|
cannedKeyAlgoNames = cannedKeyAlgoRsaNames;
|
|
cannedKeyAlgoNamesSz = cannedKeyAlgoRsaNamesSz;
|
|
}
|
|
payloadSz = MSG_ID_SZ + COOKIE_SZ + (LENGTH_SZ * 11) + BOOLEAN_SZ +
|
|
cannedKexAlgoNamesSz + cannedKeyAlgoNamesSz +
|
|
(cannedEncAlgoNamesSz * 2) +
|
|
(cannedMacAlgoNamesSz * 2) +
|
|
(cannedNoneNamesSz * 2);
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
payload = output + idx;
|
|
|
|
output[idx++] = MSGID_KEXINIT;
|
|
|
|
ret = wc_RNG_GenerateBlock(ssh->rng, output + idx, COOKIE_SZ);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
byte* buf;
|
|
word32 bufSz = payloadSz + LENGTH_SZ;
|
|
|
|
idx += COOKIE_SZ;
|
|
|
|
CopyNameList(output, &idx, cannedKexAlgoNames, cannedKexAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedKeyAlgoNames, cannedKeyAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedEncAlgoNames, cannedEncAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedEncAlgoNames, cannedEncAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedMacAlgoNames, cannedMacAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedMacAlgoNames, cannedMacAlgoNamesSz);
|
|
CopyNameList(output, &idx, cannedNoneNames, cannedNoneNamesSz);
|
|
CopyNameList(output, &idx, cannedNoneNames, cannedNoneNamesSz);
|
|
c32toa(0, output + idx); /* Languages - Client To Server (0) */
|
|
idx += LENGTH_SZ;
|
|
c32toa(0, output + idx); /* Languages - Server To Client (0) */
|
|
idx += LENGTH_SZ;
|
|
output[idx++] = 0; /* First KEX packet follows (false) */
|
|
c32toa(0, output + idx); /* Reserved (0) */
|
|
idx += LENGTH_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
buf = (byte*)WMALLOC(bufSz, ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (buf == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Cannot allocate storage for KEX Init msg");
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
else {
|
|
c32toa(payloadSz, buf);
|
|
WMEMCPY(buf + LENGTH_SZ, payload, payloadSz);
|
|
ssh->handshake->kexInit = buf;
|
|
ssh->handshake->kexInitSz = bufSz;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = BundlePacket(ssh);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendKexInit(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* SendKexDhReply()
|
|
* It is also the funciton used for MSGID_KEXECDH_REPLY. The parameters
|
|
* are analogous between the two messages. Where MSGID_KEXDH_REPLY has
|
|
* server's public host key (K_S), f, and the signature of H;
|
|
* MSGID_KEXECDH_REPLY has K_S, the server'e ephemeral public key (Q_S),
|
|
* and the signature of H. This also applies to the GEX version of this.
|
|
* H is calculated the same for KEXDH and KEXECDH, and has some exceptions
|
|
* for GEXDH. */
|
|
int SendKexDhReply(WOLFSSH* ssh)
|
|
{
|
|
/* This function and DoKexDhReply() are unwieldy and in need of refactoring. */
|
|
const byte* primeGroup = dhPrimeGroup14;
|
|
word32 primeGroupSz = dhPrimeGroup14Sz;
|
|
const byte* generator = dhGenerator;
|
|
word32 generatorSz = dhGeneratorSz;
|
|
|
|
byte useEcc = 0;
|
|
byte f[257];
|
|
word32 fSz = sizeof(f);
|
|
byte fPad = 0;
|
|
byte kPad = 0;
|
|
|
|
struct {
|
|
byte useRsa;
|
|
word32 sz;
|
|
const char *name;
|
|
word32 nameSz;
|
|
union {
|
|
struct {
|
|
RsaKey key;
|
|
byte e[257];
|
|
word32 eSz;
|
|
byte ePad;
|
|
byte n[257];
|
|
word32 nSz;
|
|
byte nPad;
|
|
} rsa;
|
|
struct {
|
|
ecc_key key;
|
|
word32 keyBlobSz;
|
|
const char *keyBlobName;
|
|
word32 keyBlobNameSz;
|
|
byte q[257];
|
|
word32 qSz;
|
|
byte qPad;
|
|
const char *primeName;
|
|
word32 primeNameSz;
|
|
} ecc;
|
|
} sk;
|
|
} sigKeyBlock;
|
|
|
|
byte sig[512];
|
|
word32 sigSz = sizeof(sig);
|
|
word32 sigBlockSz;
|
|
|
|
word32 payloadSz;
|
|
byte scratchLen[LENGTH_SZ];
|
|
word32 scratch = 0;
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
byte msgId = MSGID_KEXDH_REPLY;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendKexDhReply()");
|
|
|
|
if (ssh == NULL || ssh->handshake == NULL) {
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
sigKeyBlock.useRsa = ssh->handshake->pubKeyId == ID_SSH_RSA;
|
|
sigKeyBlock.name = IdToName(ssh->handshake->pubKeyId);
|
|
sigKeyBlock.nameSz = (word32)strlen(sigKeyBlock.name);
|
|
|
|
switch (ssh->handshake->kexId) {
|
|
case ID_DH_GROUP1_SHA1:
|
|
primeGroup = dhPrimeGroup1;
|
|
primeGroupSz = dhPrimeGroup1Sz;
|
|
break;
|
|
|
|
case ID_DH_GROUP14_SHA1:
|
|
/* This is the default case. */
|
|
break;
|
|
|
|
case ID_DH_GEX_SHA256:
|
|
msgId = MSGID_KEXDH_GEX_REPLY;
|
|
break;
|
|
|
|
case ID_ECDH_SHA2_NISTP256:
|
|
case ID_ECDH_SHA2_NISTP384:
|
|
case ID_ECDH_SHA2_NISTP521:
|
|
useEcc = 1;
|
|
msgId = MSGID_KEXDH_REPLY;
|
|
break;
|
|
|
|
default:
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
/* At this point, the exchange hash, H, includes items V_C, V_S, I_C,
|
|
* and I_S. Next add K_S, the server's public host key. K_S will
|
|
* either be RSA or ECDSA public key blob. */
|
|
if (ret == WS_SUCCESS) {
|
|
if (sigKeyBlock.useRsa) {
|
|
/* Decode the user-configured RSA private key. */
|
|
sigKeyBlock.sk.rsa.eSz = sizeof(sigKeyBlock.sk.rsa.e);
|
|
sigKeyBlock.sk.rsa.nSz = sizeof(sigKeyBlock.sk.rsa.n);
|
|
ret = wc_InitRsaKey(&sigKeyBlock.sk.rsa.key, ssh->ctx->heap);
|
|
if (ret == 0)
|
|
ret = wc_RsaPrivateKeyDecode(ssh->ctx->privateKey, &scratch,
|
|
&sigKeyBlock.sk.rsa.key,
|
|
(int)ssh->ctx->privateKeySz);
|
|
/* Flatten the public key into mpint values for the hash. */
|
|
if (ret == 0)
|
|
ret = wc_RsaFlattenPublicKey(&sigKeyBlock.sk.rsa.key,
|
|
sigKeyBlock.sk.rsa.e,
|
|
&sigKeyBlock.sk.rsa.eSz,
|
|
sigKeyBlock.sk.rsa.n,
|
|
&sigKeyBlock.sk.rsa.nSz);
|
|
if (ret == 0) {
|
|
/* Add a pad byte if the mpint has the MSB set. */
|
|
CreateMpint(sigKeyBlock.sk.rsa.e, &sigKeyBlock.sk.rsa.eSz,
|
|
&sigKeyBlock.sk.rsa.ePad);
|
|
CreateMpint(sigKeyBlock.sk.rsa.n, &sigKeyBlock.sk.rsa.nSz,
|
|
&sigKeyBlock.sk.rsa.nPad);
|
|
sigKeyBlock.sz = (LENGTH_SZ * 3) + sigKeyBlock.nameSz +
|
|
sigKeyBlock.sk.rsa.eSz +
|
|
sigKeyBlock.sk.rsa.ePad +
|
|
sigKeyBlock.sk.rsa.nSz +
|
|
sigKeyBlock.sk.rsa.nPad;
|
|
c32toa(sigKeyBlock.sz, scratchLen);
|
|
/* Hash in the length of the public key block. */
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the length of the key type string. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.nameSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the key type string. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
(byte*)sigKeyBlock.name,
|
|
sigKeyBlock.nameSz);
|
|
/* Hash in the length of the RSA public key E value. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.sk.rsa.eSz + sigKeyBlock.sk.rsa.ePad,
|
|
scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the RSA public key E value. */
|
|
if (ret == 0) {
|
|
if (sigKeyBlock.sk.rsa.ePad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the RSA public key E value. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
sigKeyBlock.sk.rsa.e,
|
|
sigKeyBlock.sk.rsa.eSz);
|
|
/* Hash in the length of the RSA public key N value. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.sk.rsa.nSz + sigKeyBlock.sk.rsa.nPad,
|
|
scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the RSA public key N value. */
|
|
if (ret == 0) {
|
|
if (sigKeyBlock.sk.rsa.nPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the RSA public key N value. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
sigKeyBlock.sk.rsa.n,
|
|
sigKeyBlock.sk.rsa.nSz);
|
|
}
|
|
else {
|
|
sigKeyBlock.sk.ecc.primeName =
|
|
PrimeNameForId(ssh->handshake->pubKeyId);
|
|
sigKeyBlock.sk.ecc.primeNameSz =
|
|
(word32)strlen(sigKeyBlock.sk.ecc.primeName);
|
|
|
|
/* Decode the user-configured ECDSA private key. */
|
|
sigKeyBlock.sk.ecc.qSz = sizeof(sigKeyBlock.sk.ecc.q);
|
|
ret = wc_ecc_init_ex(&sigKeyBlock.sk.ecc.key, ssh->ctx->heap,
|
|
INVALID_DEVID);
|
|
scratch = 0;
|
|
if (ret == 0)
|
|
ret = wc_EccPrivateKeyDecode(ssh->ctx->privateKey, &scratch,
|
|
&sigKeyBlock.sk.ecc.key,
|
|
ssh->ctx->privateKeySz);
|
|
/* Flatten the public key into x963 value for the exchange hash. */
|
|
if (ret == 0)
|
|
ret = wc_ecc_export_x963(&sigKeyBlock.sk.ecc.key,
|
|
sigKeyBlock.sk.ecc.q,
|
|
&sigKeyBlock.sk.ecc.qSz);
|
|
/* Hash in the length of the public key block. */
|
|
if (ret == 0) {
|
|
sigKeyBlock.sz = (LENGTH_SZ * 3) +
|
|
sigKeyBlock.nameSz +
|
|
sigKeyBlock.sk.ecc.primeNameSz +
|
|
sigKeyBlock.sk.ecc.qSz;
|
|
c32toa(sigKeyBlock.sz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the length of the key type string. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.nameSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the key type string. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
(byte*)sigKeyBlock.name,
|
|
sigKeyBlock.nameSz);
|
|
/* Hash in the length of the name of the prime. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.sk.ecc.primeNameSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the name of the prime. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
(const byte*)sigKeyBlock.sk.ecc.primeName,
|
|
sigKeyBlock.sk.ecc.primeNameSz);
|
|
/* Hash in the length of the public key. */
|
|
if (ret == 0) {
|
|
c32toa(sigKeyBlock.sk.ecc.qSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the public key. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
sigKeyBlock.sk.ecc.q,
|
|
sigKeyBlock.sk.ecc.qSz);
|
|
}
|
|
|
|
/* If using DH-GEX include the GEX specific values. */
|
|
if (ssh->handshake->kexId == ID_DH_GEX_SHA256) {
|
|
byte primeGroupPad = 0, generatorPad = 0;
|
|
|
|
/* Hash in the client's requested minimum key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexMinSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's requested preferred key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexPreferredSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's requested maximum key size. */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->dhGexMaxSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Add a pad byte if the mpint has the MSB set. */
|
|
if (ret == 0) {
|
|
CreateMpint((byte*)primeGroup, &primeGroupSz, &primeGroupPad);
|
|
|
|
/* Hash in the length of the GEX prime group. */
|
|
c32toa(primeGroupSz + primeGroupPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the GEX prime group. */
|
|
if (ret == 0) {
|
|
if (primeGroupPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the GEX prime group. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
primeGroup, primeGroupSz);
|
|
/* Add a pad byte if the mpint has the MSB set. */
|
|
if (ret == 0) {
|
|
CreateMpint((byte*)generator, &generatorSz, &generatorPad);
|
|
|
|
/* Hash in the length of the GEX generator. */
|
|
c32toa(generatorSz + generatorPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the pad byte for the GEX generator. */
|
|
if (ret == 0) {
|
|
if (generatorPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, 1);
|
|
}
|
|
}
|
|
/* Hash in the GEX generator. */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId,
|
|
generator, generatorSz);
|
|
}
|
|
|
|
/* Hash in the size of the client's DH e-value (ECDH Q-value). */
|
|
if (ret == 0) {
|
|
c32toa(ssh->handshake->eSz, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
/* Hash in the client's DH e-value (ECDH Q-value). */
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->handshake->e, ssh->handshake->eSz);
|
|
|
|
/* Make the server's DH f-value and the shared secret K. */
|
|
/* Or make the server's ECDH private value, and the shared secret K. */
|
|
if (ret == 0) {
|
|
if (!useEcc) {
|
|
DhKey privKey;
|
|
byte y[256];
|
|
word32 ySz = sizeof(y);
|
|
|
|
ret = wc_InitDhKey(&privKey);
|
|
if (ret == 0)
|
|
ret = wc_DhSetKey(&privKey, primeGroup, primeGroupSz,
|
|
generator, generatorSz);
|
|
if (ret == 0)
|
|
ret = wc_DhGenerateKeyPair(&privKey, ssh->rng,
|
|
y, &ySz, f, &fSz);
|
|
if (ret == 0)
|
|
ret = wc_DhAgree(&privKey, ssh->k, &ssh->kSz, y, ySz,
|
|
ssh->handshake->e, ssh->handshake->eSz);
|
|
ForceZero(y, ySz);
|
|
wc_FreeDhKey(&privKey);
|
|
}
|
|
else {
|
|
ecc_key pubKey;
|
|
ecc_key privKey;
|
|
int primeId = wcPrimeForId(ssh->handshake->kexId);
|
|
|
|
if (primeId == ECC_CURVE_INVALID)
|
|
ret = WS_INVALID_PRIME_CURVE;
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_init_ex(&pubKey, ssh->ctx->heap,
|
|
INVALID_DEVID);
|
|
if (ret == 0)
|
|
ret = wc_ecc_init_ex(&privKey, ssh->ctx->heap,
|
|
INVALID_DEVID);
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_import_x963_ex(ssh->handshake->e,
|
|
ssh->handshake->eSz,
|
|
&pubKey, primeId);
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_make_key_ex(ssh->rng,
|
|
wc_ecc_get_curve_size_from_id(primeId),
|
|
&privKey, primeId);
|
|
if (ret == 0)
|
|
ret = wc_ecc_export_x963(&privKey, f, &fSz);
|
|
if (ret == 0)
|
|
ret = wc_ecc_shared_secret(&privKey, &pubKey,
|
|
ssh->k, &ssh->kSz);
|
|
wc_ecc_free(&privKey);
|
|
wc_ecc_free(&pubKey);
|
|
}
|
|
}
|
|
|
|
/* Hash in the server's DH f-value. */
|
|
if (ret == 0) {
|
|
CreateMpint(f, &fSz, &fPad);
|
|
c32toa(fSz + fPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
if (ret == 0) {
|
|
if (fPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, scratchLen, 1);
|
|
}
|
|
}
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, f, fSz);
|
|
|
|
/* Hash in the shared secret K. */
|
|
if (ret == 0) {
|
|
CreateMpint(ssh->k, &ssh->kSz, &kPad);
|
|
c32toa(ssh->kSz + kPad, scratchLen);
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
scratchLen, LENGTH_SZ);
|
|
}
|
|
if (ret == 0) {
|
|
if (kPad) {
|
|
scratchLen[0] = 0;
|
|
ret = wc_HashUpdate(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, scratchLen, 1);
|
|
}
|
|
}
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&ssh->handshake->hash, (enum wc_HashType)ssh->handshake->hashId,
|
|
ssh->k, ssh->kSz);
|
|
|
|
/* Save the exchange hash value H, and session ID. */
|
|
if (ret == 0)
|
|
ret = wc_HashFinal(&ssh->handshake->hash,
|
|
(enum wc_HashType)ssh->handshake->hashId, ssh->h);
|
|
if (ret == 0) {
|
|
ssh->hSz = wc_HashGetDigestSize((enum wc_HashType)ssh->handshake->hashId);
|
|
if (ssh->sessionIdSz == 0) {
|
|
WMEMCPY(ssh->sessionId, ssh->h, ssh->hSz);
|
|
ssh->sessionIdSz = ssh->hSz;
|
|
}
|
|
}
|
|
|
|
if (ret != WS_SUCCESS)
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
|
|
/* Sign h with the server's private key. */
|
|
if (ret == WS_SUCCESS) {
|
|
wc_HashAlg digestHash;
|
|
byte digest[WC_MAX_DIGEST_SIZE];
|
|
byte sigHashId;
|
|
|
|
sigHashId = HashForId(ssh->handshake->pubKeyId);
|
|
|
|
ret = wc_HashInit(&digestHash, (enum wc_HashType)sigHashId);
|
|
if (ret == 0)
|
|
ret = wc_HashUpdate(&digestHash, (enum wc_HashType)sigHashId, ssh->h, ssh->hSz);
|
|
if (ret == 0)
|
|
ret = wc_HashFinal(&digestHash, (enum wc_HashType)sigHashId, digest);
|
|
if (ret != 0)
|
|
ret = WS_CRYPTO_FAILED;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (sigKeyBlock.useRsa) {
|
|
byte encSig[MAX_ENCODED_SIG_SZ];
|
|
word32 encSigSz;
|
|
|
|
encSigSz = wc_EncodeSignature(encSig, digest,
|
|
wc_HashGetDigestSize((enum wc_HashType)sigHashId),
|
|
wc_HashGetOID((enum wc_HashType)sigHashId));
|
|
if (encSigSz <= 0) {
|
|
WLOG(WS_LOG_DEBUG, "SendKexDhReply: Bad Encode Sig");
|
|
ret = WS_CRYPTO_FAILED;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_INFO, "Signing hash with RSA.");
|
|
sigSz = wc_RsaSSL_Sign(encSig, encSigSz, sig, sizeof(sig),
|
|
&sigKeyBlock.sk.rsa.key, ssh->rng);
|
|
if (sigSz <= 0) {
|
|
WLOG(WS_LOG_DEBUG, "SendKexDhReply: Bad RSA Sign");
|
|
ret = WS_RSA_E;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_INFO, "Signing hash with ECDSA.");
|
|
sigSz = sizeof(sig);
|
|
ret = wc_ecc_sign_hash(digest, wc_HashGetDigestSize((enum wc_HashType)sigHashId),
|
|
sig, &sigSz,
|
|
ssh->rng, &sigKeyBlock.sk.ecc.key);
|
|
if (ret != MP_OKAY) {
|
|
WLOG(WS_LOG_DEBUG, "SendKexDhReply: Bad ECDSA Sign");
|
|
ret = WS_ECC_E;
|
|
}
|
|
else {
|
|
byte r[257];
|
|
word32 rSz = sizeof(r);
|
|
byte rPad;
|
|
byte s[257];
|
|
word32 sSz = sizeof(s);
|
|
byte sPad;
|
|
|
|
ret = wc_ecc_sig_to_rs(sig, sigSz, r, &rSz, s, &sSz);
|
|
if (ret == 0) {
|
|
idx = 0;
|
|
rPad = (r[0] & 0x80) ? 1 : 0;
|
|
sPad = (s[0] & 0x80) ? 1 : 0;
|
|
sigSz = (LENGTH_SZ * 2) + rSz + rPad + sSz + sPad;
|
|
|
|
c32toa(rSz + rPad, sig + idx);
|
|
idx += LENGTH_SZ;
|
|
if (rPad)
|
|
sig[idx++] = 0;
|
|
WMEMCPY(sig + idx, r, rSz);
|
|
idx += rSz;
|
|
c32toa(sSz + sPad, sig + idx);
|
|
idx += LENGTH_SZ;
|
|
if (sPad)
|
|
sig[idx++] = 0;
|
|
WMEMCPY(sig + idx, s, sSz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sigKeyBlock.useRsa)
|
|
wc_FreeRsaKey(&sigKeyBlock.sk.rsa.key);
|
|
else
|
|
wc_ecc_free(&sigKeyBlock.sk.ecc.key);
|
|
|
|
sigBlockSz = (LENGTH_SZ * 2) + sigKeyBlock.nameSz + sigSz;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GenerateKeys(ssh);
|
|
|
|
/* Get the buffer, copy the packet data, once f is laid into the buffer,
|
|
* add it to the hash and then add K. */
|
|
if (ret == WS_SUCCESS) {
|
|
payloadSz = MSG_ID_SZ + (LENGTH_SZ * 3) +
|
|
sigKeyBlock.sz + fSz + fPad + sigBlockSz;
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = msgId;
|
|
|
|
/* Copy the rsaKeyBlock into the buffer. */
|
|
c32toa(sigKeyBlock.sz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
c32toa(sigKeyBlock.nameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, sigKeyBlock.name, sigKeyBlock.nameSz);
|
|
idx += sigKeyBlock.nameSz;
|
|
if (sigKeyBlock.useRsa) {
|
|
c32toa(sigKeyBlock.sk.rsa.eSz + sigKeyBlock.sk.rsa.ePad,
|
|
output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (sigKeyBlock.sk.rsa.ePad) output[idx++] = 0;
|
|
WMEMCPY(output + idx, sigKeyBlock.sk.rsa.e, sigKeyBlock.sk.rsa.eSz);
|
|
idx += sigKeyBlock.sk.rsa.eSz;
|
|
c32toa(sigKeyBlock.sk.rsa.nSz + sigKeyBlock.sk.rsa.nPad,
|
|
output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (sigKeyBlock.sk.rsa.nPad) output[idx++] = 0;
|
|
WMEMCPY(output + idx, sigKeyBlock.sk.rsa.n, sigKeyBlock.sk.rsa.nSz);
|
|
idx += sigKeyBlock.sk.rsa.nSz;
|
|
}
|
|
else {
|
|
c32toa(sigKeyBlock.sk.ecc.primeNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, sigKeyBlock.sk.ecc.primeName,
|
|
sigKeyBlock.sk.ecc.primeNameSz);
|
|
idx += sigKeyBlock.sk.ecc.primeNameSz;
|
|
c32toa(sigKeyBlock.sk.ecc.qSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, sigKeyBlock.sk.ecc.q,
|
|
sigKeyBlock.sk.ecc.qSz);
|
|
idx += sigKeyBlock.sk.ecc.qSz;
|
|
}
|
|
|
|
/* Copy the server's public key. F for DE, or Q_S for ECDH. */
|
|
c32toa(fSz + fPad, output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (fPad) output[idx++] = 0;
|
|
WMEMCPY(output + idx, f, fSz);
|
|
idx += fSz;
|
|
|
|
/* Copy the signature of the exchange hash. */
|
|
c32toa(sigBlockSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
c32toa(sigKeyBlock.nameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, sigKeyBlock.name, sigKeyBlock.nameSz);
|
|
idx += sigKeyBlock.nameSz;
|
|
c32toa(sigSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, sig, sigSz);
|
|
idx += sigSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = SendNewKeys(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhReply(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendNewKeys(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendNewKeys()");
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_NEWKEYS;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->blockSz = ssh->handshake->blockSz;
|
|
ssh->encryptId = ssh->handshake->encryptId;
|
|
ssh->macSz = ssh->handshake->macSz;
|
|
ssh->macId = ssh->handshake->macId;
|
|
ssh->aeadMode = ssh->handshake->aeadMode;
|
|
WMEMCPY(&ssh->keys, &ssh->handshake->keys, sizeof(Keys));
|
|
|
|
switch (ssh->encryptId) {
|
|
case ID_NONE:
|
|
WLOG(WS_LOG_DEBUG, "SNK: using cipher none");
|
|
break;
|
|
|
|
#ifdef HAVE_AES_CBC
|
|
case ID_AES128_CBC:
|
|
#endif
|
|
#ifdef WOLFSSL_AES_COUNTER
|
|
case ID_AES128_CTR:
|
|
#endif
|
|
#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_COUNTER)
|
|
WLOG(WS_LOG_DEBUG, "SNK: using cipher aes128-cbc/ctr");
|
|
ret = wc_AesSetKey(&ssh->encryptCipher.aes,
|
|
ssh->keys.encKey, ssh->keys.encKeySz,
|
|
ssh->keys.iv, AES_ENCRYPTION);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef HAVE_AESGCM
|
|
case ID_AES128_GCM:
|
|
WLOG(WS_LOG_DEBUG, "SNK: using cipher aes128-gcm");
|
|
ret = wc_AesGcmSetKey(&ssh->encryptCipher.aes,
|
|
ssh->keys.encKey, ssh->keys.encKeySz);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "SNK: using cipher invalid");
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->txCount = 0;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendNewKeys(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendKexDhGexRequest(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
word32 payloadSz;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendKexDhGexRequest()");
|
|
if (ssh == NULL || ssh->handshake == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
payloadSz = MSG_ID_SZ + (UINT32_SZ * 3);
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_KEXDH_GEX_REQUEST;
|
|
|
|
c32toa(ssh->handshake->dhGexMinSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(ssh->handshake->dhGexPreferredSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(ssh->handshake->dhGexMaxSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhGexRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendKexDhGexGroup(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
word32 payloadSz;
|
|
const byte* primeGroup = dhPrimeGroup14;
|
|
word32 primeGroupSz = dhPrimeGroup14Sz;
|
|
const byte* generator = dhGenerator;
|
|
word32 generatorSz = dhGeneratorSz;
|
|
byte primePad = 0;
|
|
byte generatorPad = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendKexDhGexGroup()");
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (primeGroup[0] & 0x80)
|
|
primePad = 1;
|
|
|
|
if (generator[0] & 0x80)
|
|
generatorPad = 1;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
payloadSz = MSG_ID_SZ + (LENGTH_SZ * 2) +
|
|
primeGroupSz + primePad +
|
|
generatorSz + generatorPad;
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_KEXDH_GEX_GROUP;
|
|
|
|
c32toa(primeGroupSz + primePad, output + idx);
|
|
idx += LENGTH_SZ;
|
|
|
|
if (primePad) {
|
|
output[idx] = 0;
|
|
idx += 1;
|
|
}
|
|
|
|
WMEMCPY(output + idx, primeGroup, primeGroupSz);
|
|
idx += primeGroupSz;
|
|
|
|
c32toa(generatorSz + generatorPad, output + idx);
|
|
idx += LENGTH_SZ;
|
|
|
|
if (generatorPad) {
|
|
output[idx] = 0;
|
|
idx += 1;
|
|
}
|
|
|
|
WMEMCPY(output + idx, generator, generatorSz);
|
|
idx += generatorSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhGexGroup(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendKexDhInit(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
word32 payloadSz;
|
|
const byte* primeGroup = dhPrimeGroup14;
|
|
word32 primeGroupSz = dhPrimeGroup14Sz;
|
|
const byte* generator = dhGenerator;
|
|
word32 generatorSz = dhGeneratorSz;
|
|
int ret = WS_SUCCESS;
|
|
byte msgId = MSGID_KEXDH_INIT;
|
|
byte e[256];
|
|
word32 eSz = sizeof(e);
|
|
byte ePad = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendKexDhInit()");
|
|
|
|
switch (ssh->handshake->kexId) {
|
|
case ID_DH_GROUP1_SHA1:
|
|
primeGroup = dhPrimeGroup1;
|
|
primeGroupSz = dhPrimeGroup1Sz;
|
|
break;
|
|
|
|
case ID_DH_GROUP14_SHA1:
|
|
/* This is the default case. */
|
|
break;
|
|
|
|
case ID_DH_GEX_SHA256:
|
|
primeGroup = ssh->handshake->primeGroup;
|
|
primeGroupSz = ssh->handshake->primeGroupSz;
|
|
generator = ssh->handshake->generator;
|
|
generatorSz = ssh->handshake->generatorSz;
|
|
msgId = MSGID_KEXDH_GEX_INIT;
|
|
break;
|
|
|
|
case ID_ECDH_SHA2_NISTP256:
|
|
case ID_ECDH_SHA2_NISTP384:
|
|
case ID_ECDH_SHA2_NISTP521:
|
|
ssh->handshake->useEcc = 1;
|
|
msgId = MSGID_KEXECDH_INIT;
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "Invalid algo: %u", ssh->handshake->kexId);
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (!ssh->handshake->useEcc) {
|
|
DhKey* privKey = &ssh->handshake->privKey.dh;
|
|
|
|
ret = wc_InitDhKey(privKey);
|
|
if (ret == 0)
|
|
ret = wc_DhSetKey(privKey, primeGroup, primeGroupSz,
|
|
generator, generatorSz);
|
|
if (ret == 0)
|
|
ret = wc_DhGenerateKeyPair(privKey, ssh->rng,
|
|
ssh->handshake->x,
|
|
&ssh->handshake->xSz,
|
|
e, &eSz);
|
|
}
|
|
else {
|
|
ecc_key* privKey = &ssh->handshake->privKey.ecc;
|
|
int primeId = wcPrimeForId(ssh->handshake->kexId);
|
|
|
|
if (primeId == ECC_CURVE_INVALID)
|
|
ret = WS_INVALID_PRIME_CURVE;
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_init_ex(privKey, ssh->ctx->heap,
|
|
INVALID_DEVID);
|
|
|
|
if (ret == 0)
|
|
ret = wc_ecc_make_key_ex(ssh->rng,
|
|
wc_ecc_get_curve_size_from_id(primeId),
|
|
privKey, primeId);
|
|
if (ret == 0)
|
|
ret = wc_ecc_export_x963(privKey, e, &eSz);
|
|
}
|
|
|
|
if (ret == 0)
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
CreateMpint(e, &eSz, &ePad);
|
|
if (ePad == 1) {
|
|
ssh->handshake->e[0] = 0;
|
|
}
|
|
WMEMCPY(ssh->handshake->e + ePad, e, eSz);
|
|
ssh->handshake->eSz = eSz + ePad;
|
|
|
|
payloadSz = MSG_ID_SZ + LENGTH_SZ + eSz + ePad;
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = msgId;
|
|
|
|
c32toa(eSz + ePad, output + idx);
|
|
idx += LENGTH_SZ;
|
|
|
|
if (ePad) {
|
|
output[idx] = 0;
|
|
idx += 1;
|
|
}
|
|
|
|
WMEMCPY(output + idx, e, eSz);
|
|
idx += eSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhInit(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendUnimplemented(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Entering SendUnimplemented(), peerSeq = %u", ssh->peerSeq);
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_UNIMPLEMENTED;
|
|
c32toa(ssh->peerSeq, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendUnimplemented(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendDisconnect(WOLFSSH* ssh, word32 reason)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + (LENGTH_SZ * 2));
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_DISCONNECT;
|
|
c32toa(reason, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(0, output + idx);
|
|
idx += LENGTH_SZ;
|
|
c32toa(0, output + idx);
|
|
idx += LENGTH_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendIgnore(WOLFSSH* ssh, const unsigned char* data, word32 dataSz)
|
|
{
|
|
byte* output;
|
|
word32 idx = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL || (data == NULL && dataSz > 0))
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + dataSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_IGNORE;
|
|
c32toa(dataSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (dataSz > 0) {
|
|
WMEMCPY(output + idx, data, dataSz);
|
|
idx += dataSz;
|
|
}
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const char cannedLangTag[] = "en-us";
|
|
static const word32 cannedLangTagSz = sizeof(cannedLangTag) - 1;
|
|
|
|
|
|
int SendDebug(WOLFSSH* ssh, byte alwaysDisplay, const char* msg)
|
|
{
|
|
word32 msgSz;
|
|
byte* output;
|
|
word32 idx = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
msgSz = (msg != NULL) ? (word32)WSTRLEN(msg) : 0;
|
|
|
|
ret = PreparePacket(ssh,
|
|
MSG_ID_SZ + BOOLEAN_SZ + (LENGTH_SZ * 2) +
|
|
msgSz + cannedLangTagSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_DEBUG;
|
|
output[idx++] = (alwaysDisplay != 0);
|
|
c32toa(msgSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (msgSz > 0) {
|
|
WMEMCPY(output + idx, msg, msgSz);
|
|
idx += msgSz;
|
|
}
|
|
c32toa(cannedLangTagSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, cannedLangTag, cannedLangTagSz);
|
|
idx += cannedLangTagSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendServiceRequest(WOLFSSH* ssh, byte serviceId)
|
|
{
|
|
const char* serviceName;
|
|
word32 serviceNameSz;
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendServiceRequest()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
serviceName = IdToName(serviceId);
|
|
serviceNameSz = (word32)WSTRLEN(serviceName);
|
|
|
|
ret = PreparePacket(ssh,
|
|
MSG_ID_SZ + LENGTH_SZ + serviceNameSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_SERVICE_REQUEST;
|
|
c32toa(serviceNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, serviceName, serviceNameSz);
|
|
idx += serviceNameSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendServiceRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendServiceAccept(WOLFSSH* ssh, byte serviceId)
|
|
{
|
|
const char* serviceName;
|
|
word32 serviceNameSz;
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
serviceName = IdToName(serviceId);
|
|
serviceNameSz = (word32)WSTRLEN(serviceName);
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + serviceNameSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_SERVICE_ACCEPT;
|
|
c32toa(serviceNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, serviceName, serviceNameSz);
|
|
idx += serviceNameSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = SendUserAuthBanner(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const char cannedAuths[] = "publickey,password";
|
|
static const word32 cannedAuthsSz = sizeof(cannedAuths) - 1;
|
|
|
|
|
|
int SendUserAuthRequest(WOLFSSH* ssh, byte authId)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
const char* authName;
|
|
word32 authNameSz;
|
|
const char* serviceName;
|
|
word32 serviceNameSz;
|
|
word32 payloadSz;
|
|
int ret = WS_SUCCESS;
|
|
WS_UserAuthData authData;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendUserAuthRequest()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WMEMSET(&authData, 0, sizeof(WS_UserAuthData));
|
|
if (authId == ID_USERAUTH_PASSWORD && ssh->ctx->userAuthCb != NULL) {
|
|
WLOG(WS_LOG_DEBUG, "SUARPW: Calling the userauth callback");
|
|
ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PASSWORD,
|
|
&authData, ssh->userAuthCtx);
|
|
if (ret != WOLFSSH_USERAUTH_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "SUARPW: Couldn't get password");
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
serviceName = IdToName(ID_SERVICE_CONNECTION);
|
|
serviceNameSz = (word32)WSTRLEN(serviceName);
|
|
authName = IdToName(authId);
|
|
authNameSz = (word32)WSTRLEN(authName);
|
|
|
|
payloadSz = MSG_ID_SZ + (LENGTH_SZ * 3) +
|
|
ssh->userNameSz + serviceNameSz + authNameSz;
|
|
|
|
if (authId == ID_USERAUTH_PASSWORD) {
|
|
payloadSz += BOOLEAN_SZ + LENGTH_SZ +
|
|
authData.sf.password.passwordSz;
|
|
}
|
|
else if (authId != ID_NONE)
|
|
ret = WS_INVALID_ALGO_ID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, payloadSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_USERAUTH_REQUEST;
|
|
c32toa(ssh->userNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, ssh->userName, ssh->userNameSz);
|
|
idx += ssh->userNameSz;
|
|
|
|
c32toa(serviceNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, serviceName, serviceNameSz);
|
|
idx += serviceNameSz;
|
|
|
|
c32toa(authNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, authName, authNameSz);
|
|
idx += authNameSz;
|
|
|
|
if (authId == ID_USERAUTH_PASSWORD) {
|
|
output[idx++] = 0; /* Boolean "FALSE" for password change */
|
|
c32toa(authData.sf.password.passwordSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, authData.sf.password.password,
|
|
authData.sf.password.passwordSz);
|
|
idx += authData.sf.password.passwordSz;
|
|
}
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
ForceZero(&authData, sizeof(WS_UserAuthData));
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendUserAuthRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendUserAuthFailure(WOLFSSH* ssh, byte partialSuccess)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendUserAuthFailure()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh,
|
|
MSG_ID_SZ + LENGTH_SZ +
|
|
cannedAuthsSz + BOOLEAN_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_USERAUTH_FAILURE;
|
|
c32toa(cannedAuthsSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, cannedAuths, cannedAuthsSz);
|
|
idx += cannedAuthsSz;
|
|
output[idx++] = (partialSuccess != 0);
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendUserAuthSuccess(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_USERAUTH_SUCCESS;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendUserAuthPkOk(WOLFSSH* ssh,
|
|
const byte* algoName, word32 algoNameSz,
|
|
const byte* publicKey, word32 publicKeySz)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL ||
|
|
algoName == NULL || algoNameSz == 0 ||
|
|
publicKey == NULL || publicKeySz == 0) {
|
|
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + (LENGTH_SZ * 2) +
|
|
algoNameSz + publicKeySz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_USERAUTH_PK_OK;
|
|
c32toa(algoNameSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, algoName, algoNameSz);
|
|
idx += algoNameSz;
|
|
c32toa(publicKeySz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, publicKey, publicKeySz);
|
|
idx += publicKeySz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendUserAuthBanner(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
const char* banner = NULL;
|
|
word32 bannerSz = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendUserAuthBanner()");
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
banner = ssh->ctx->banner;
|
|
bannerSz = ssh->ctx->bannerSz;
|
|
}
|
|
|
|
if (banner != NULL && bannerSz > 0) {
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + (LENGTH_SZ * 2) +
|
|
bannerSz + cannedLangTagSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_USERAUTH_BANNER;
|
|
c32toa(bannerSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
if (bannerSz > 0)
|
|
WMEMCPY(output + idx, banner, bannerSz);
|
|
idx += bannerSz;
|
|
c32toa(cannedLangTagSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, cannedLangTag, cannedLangTagSz);
|
|
idx += cannedLangTagSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendUserAuthBanner()");
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendRequestSuccess(WOLFSSH* ssh, int success)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendRequestSuccess(), %s",
|
|
success ? "Success" : "Failure");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = success ?
|
|
MSGID_REQUEST_SUCCESS : MSGID_REQUEST_FAILURE;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendRequestSuccess(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int SendChannelOpen(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel,
|
|
byte* channelData, word32 channelDataSz)
|
|
{
|
|
byte* output;
|
|
const char* channelType;
|
|
word32 channelTypeSz, idx;
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelOpen()");
|
|
|
|
if (ssh == NULL || channel == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
if (channelDataSz > 0 && channelData == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channelType = IdToName(channel->channelType);
|
|
channelTypeSz = (word32)WSTRLEN(channelType);
|
|
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + channelTypeSz +
|
|
(UINT32_SZ * 3) + channelDataSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_OPEN;
|
|
c32toa(channelTypeSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, channelType, channelTypeSz);
|
|
idx += channelTypeSz;
|
|
c32toa(channel->channel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(channel->windowSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(channel->maxPacketSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
if (channelDataSz > 0)
|
|
WMEMCPY(output + idx, channelData, channelDataSz);
|
|
idx += channelDataSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpen(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelOpenSession(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelOpenSession()");
|
|
|
|
ret = SendChannelOpen(ssh, channel, NULL, 0);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenSession(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_FWD
|
|
int SendChannelOpenForward(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
byte* forwardData = NULL;
|
|
word32 hostSz, originSz, forwardDataSz, idx;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelOpenForward()");
|
|
|
|
if (ssh == NULL || channel == NULL ||
|
|
channel->host == NULL || channel->origin == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
hostSz = (word32)WSTRLEN(channel->host);
|
|
originSz = (word32)WSTRLEN(channel->origin);
|
|
forwardDataSz = UINT32_SZ * 2 + LENGTH_SZ * 2 + hostSz + originSz;
|
|
forwardData = (byte*)WMALLOC(forwardDataSz,
|
|
ssh->ctx->heap, DYNTYPE_TEMP);
|
|
if (forwardData == NULL)
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
c32toa(hostSz, forwardData);
|
|
idx = LENGTH_SZ;
|
|
WMEMCPY(forwardData + idx, channel->host, hostSz);
|
|
idx += hostSz;
|
|
c32toa(channel->hostPort, forwardData + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(originSz, forwardData + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(forwardData + idx, channel->origin, originSz);
|
|
idx += originSz;
|
|
c32toa(channel->originPort, forwardData + idx);
|
|
|
|
ret = SendChannelOpen(ssh, channel, forwardData, forwardDataSz);
|
|
}
|
|
|
|
if (forwardData)
|
|
WFREE(forwardData, ssh->ctx->heap, DYNTYPE_TEMP);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenForward(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
#endif /* WOLFSSH_FWD */
|
|
|
|
|
|
int SendChannelOpenConf(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelOpenConf()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh,
|
|
ssh->defaultPeerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + (UINT32_SZ * 4));
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_OPEN_CONF;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(channel->channel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(channel->windowSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(channel->maxPacketSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenConf(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelEof(WOLFSSH* ssh, word32 peerChannelId)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelEof()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_EOF;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelEof(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelEow(WOLFSSH* ssh, word32 peerChannelId)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
word32 strSz = sizeof("eow@openssh.com");
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelEow()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS && !ssh->clientOpenSSH) {
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelEow(), not OpenSSH");
|
|
return ret;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + strSz +
|
|
BOOLEAN_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_REQUEST;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(strSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, "eow@openssh.com", strSz);
|
|
idx += strSz;
|
|
output[idx++] = 0; // false
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelEow(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelExit(WOLFSSH* ssh, word32 peerChannelId, int status)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
word32 strSz = sizeof("exit-status");
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelExit(), status = %d", status);
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + strSz +
|
|
BOOLEAN_SZ + UINT32_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_REQUEST;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(strSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, "exit-status", strSz);
|
|
idx += strSz;
|
|
output[idx++] = 0; // false
|
|
c32toa(status, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelExit(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelClose(WOLFSSH* ssh, word32 peerChannelId)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelClose()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, peerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
else if (channel->closeSent) {
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelClose(), already sent");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_CLOSE;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
channel->closeSent = 1;
|
|
}
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelClose(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelData(WOLFSSH* ssh, word32 peerChannel,
|
|
byte* data, word32 dataSz)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelData()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->isKeying)
|
|
ret = WS_REKEYING;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (ssh->outputBuffer.length != 0)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh, peerChannel, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Invalid peer channel");
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
word32 bound = min(channel->peerWindowSz, channel->peerMaxPacketSz);
|
|
|
|
if (dataSz > bound) {
|
|
WLOG(WS_LOG_DEBUG,
|
|
"Trying to send %u, client will only accept %u, limiting",
|
|
dataSz, bound);
|
|
dataSz = bound;
|
|
}
|
|
|
|
ret = PreparePacket(ssh,
|
|
MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_DATA;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(dataSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, data, dataSz);
|
|
idx += dataSz;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WLOG(WS_LOG_INFO, " dataSz = %u", dataSz);
|
|
WLOG(WS_LOG_INFO, " peerWindowSz = %u", channel->peerWindowSz);
|
|
channel->peerWindowSz -= dataSz;
|
|
WLOG(WS_LOG_INFO, " update peerWindowSz = %u", channel->peerWindowSz);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = dataSz;
|
|
|
|
if (ssh && ssh->error == WS_WANT_WRITE)
|
|
ssh->outputBuffer.plainSz = dataSz;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelData(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int SendChannelWindowAdjust(WOLFSSH* ssh, word32 peerChannel,
|
|
word32 bytesToAdd)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelWindowAdjust()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
channel = ChannelFind(ssh, peerChannel, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "Invalid peer channel");
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + (UINT32_SZ * 2));
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_WINDOW_ADJUST;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(bytesToAdd, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelWindowAdjust(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const char cannedShellName[] = "shell";
|
|
static const word32 cannedShellNameSz = sizeof(cannedShellName) - 1;
|
|
|
|
static const char cannedSubName[] = "subsystem";
|
|
static const word32 cannedSubNameSz = sizeof(cannedSubName) - 1;
|
|
|
|
static const char cannedExecName[] = "exec";
|
|
static const word32 cannedExecNameSz = sizeof(cannedExecName) - 1;
|
|
|
|
|
|
/* name : command for exec and name for subsystem channels */
|
|
int SendChannelRequest(WOLFSSH* ssh, byte* name, word32 nameSz)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
const char* cType = NULL;
|
|
word32 typeSz = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelRequest()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh,
|
|
ssh->defaultPeerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
switch (ssh->connectChannelId) {
|
|
case WOLFSSH_SESSION_SHELL:
|
|
cType = cannedShellName;
|
|
typeSz = cannedShellNameSz;
|
|
break;
|
|
|
|
case WOLFSSH_SESSION_EXEC:
|
|
cType = cannedExecName;
|
|
typeSz = cannedExecNameSz;
|
|
break;
|
|
|
|
case WOLFSSH_SESSION_SUBSYSTEM:
|
|
cType = cannedSubName;
|
|
typeSz = cannedSubNameSz;
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, "Unknown channel type");
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ +
|
|
typeSz + BOOLEAN_SZ +
|
|
((nameSz > 0)? UINT32_SZ : 0) + nameSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_REQUEST;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
c32toa(typeSz, output + idx);
|
|
idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, cType, typeSz);
|
|
idx += typeSz;
|
|
output[idx++] = 1;
|
|
|
|
if (nameSz > 0) {
|
|
c32toa(nameSz, output + idx);
|
|
idx += UINT32_SZ;
|
|
WMEMCPY(output + idx, name, nameSz);
|
|
idx += nameSz;
|
|
}
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
WLOG(WS_LOG_INFO, "Sending Channel Request: ");
|
|
WLOG(WS_LOG_INFO, " channelId = %u", channel->peerChannel);
|
|
WLOG(WS_LOG_INFO, " type = %s", cType);
|
|
WLOG(WS_LOG_INFO, " wantReply = %u", 1);
|
|
|
|
#ifdef DEBUG_WOLFSSH
|
|
/* only compile in code for checks on type if in debug mode */
|
|
switch (ssh->connectChannelId) {
|
|
case WOLFSSH_SESSION_EXEC:
|
|
WLOG(WS_LOG_INFO, " command = %s", name);
|
|
break;
|
|
|
|
case WOLFSSH_SESSION_SUBSYSTEM:
|
|
WLOG(WS_LOG_INFO, " subsystem = %s", name);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_TERM
|
|
/* sets terminal mode in buffer and advances idx */
|
|
static void TTYSet(word32 isSet, int type, byte* out, word32* idx)
|
|
{
|
|
if (isSet) isSet = 1;
|
|
out[*idx] = type; *idx += 1;
|
|
c32toa(isSet, out + *idx); *idx += UINT32_SZ;
|
|
}
|
|
|
|
|
|
static void TTYCharSet(char flag, int type, byte* out, word32* idx)
|
|
{
|
|
out[*idx] = type; *idx += 1;
|
|
c32toa(flag, out + *idx); *idx += UINT32_SZ;
|
|
}
|
|
|
|
|
|
/* create terminal mode string for pseudo-terminal request
|
|
* returns size of buffer */
|
|
static int CreateMode(WOLFSSH* ssh, byte* mode)
|
|
{
|
|
word32 idx = 0;
|
|
#ifndef NO_TERMIOS
|
|
WOLFSSH_TERMIOS term;
|
|
#endif
|
|
|
|
int buad = 38400; /* default speed */
|
|
|
|
#if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) && \
|
|
!defined(NO_TERMIOS)
|
|
if (tcgetattr(STDIN_FILENO, &term) != 0) {
|
|
printf("Couldn't get the original terminal settings.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* set baud rate */
|
|
buad = (int)cfgetospeed(&term);
|
|
|
|
/* char type */
|
|
TTYCharSet(term.c_cc[VINTR], WOLFSSH_VINTR, mode, &idx);
|
|
TTYCharSet(term.c_cc[VQUIT], WOLFSSH_VQUIT, mode, &idx);
|
|
TTYCharSet(term.c_cc[VERASE], WOLFSSH_VERASE, mode, &idx);
|
|
TTYCharSet(term.c_cc[VKILL], WOLFSSH_VKILL, mode, &idx);
|
|
TTYCharSet(term.c_cc[VEOF], WOLFSSH_VEOF, mode, &idx);
|
|
TTYCharSet(term.c_cc[VEOL], WOLFSSH_VEOL, mode, &idx);
|
|
TTYCharSet(term.c_cc[VEOL2], WOLFSSH_VEOL2, mode, &idx);
|
|
TTYCharSet(term.c_cc[VSTART], WOLFSSH_VSTART, mode, &idx);
|
|
TTYCharSet(term.c_cc[VSTOP], WOLFSSH_VSTOP, mode, &idx);
|
|
TTYCharSet(term.c_cc[VSUSP], WOLFSSH_VSUSP, mode, &idx);
|
|
#ifdef VDSUSP
|
|
TTYCharSet(term.c_cc[VDSUSP], WOLFSSH_VDSUSP, mode, &idx);
|
|
#endif
|
|
TTYCharSet(term.c_cc[VREPRINT], WOLFSSH_VREPRINT, mode, &idx);
|
|
TTYCharSet(term.c_cc[VWERASE], WOLFSSH_VWERASE, mode, &idx);
|
|
TTYCharSet(term.c_cc[VLNEXT], WOLFSSH_VLNEXT, mode, &idx);
|
|
#ifdef VFLUSH
|
|
TTYCharSet(term.c_cc[VFLUSH], WOLFSSH_VFLUSH, mode, &idx);
|
|
#endif
|
|
#ifdef VSWTCH
|
|
TTYCharSet(term.c_cc[VSWTCH], WOLFSSH_VSWTCH, mode, &idx);
|
|
#endif
|
|
#ifdef VSTATUS
|
|
TTYCharSet(term.c_cc[VSTATUS], WOLFSSH_VSTATUS, mode, &idx);
|
|
#endif
|
|
TTYCharSet(term.c_cc[VDISCARD], WOLFSSH_VDISCARD, mode, &idx);
|
|
|
|
|
|
/* c_iflag for input modes */
|
|
TTYSet((term.c_iflag & IGNPAR), WOLFSSH_IGNPAR, mode, &idx);
|
|
TTYSet((term.c_iflag & PARMRK), WOLFSSH_PARMRK, mode, &idx);
|
|
TTYSet((term.c_iflag & INPCK), WOLFSSH_INPCK, mode, &idx);
|
|
TTYSet((term.c_iflag & ISTRIP), WOLFSSH_ISTRIP, mode, &idx);
|
|
TTYSet((term.c_iflag & INLCR), WOLFSSH_INLCR, mode, &idx);
|
|
TTYSet((term.c_iflag & IGNCR), WOLFSSH_IGNCR, mode, &idx);
|
|
TTYSet((term.c_iflag & ICRNL), WOLFSSH_ICRNL, mode, &idx);
|
|
#ifdef IUCLC
|
|
TTYSet((term.c_iflag & IUCLC), WOLFSSH_IUCLC, mode, &idx);
|
|
#endif
|
|
TTYSet((term.c_iflag & IXON), WOLFSSH_IXON, mode, &idx);
|
|
TTYSet((term.c_iflag & IXANY), WOLFSSH_IXANY, mode, &idx);
|
|
TTYSet((term.c_iflag & IXOFF), WOLFSSH_IXOFF, mode, &idx);
|
|
TTYSet((term.c_iflag & IMAXBEL), WOLFSSH_IMAXBEL, mode, &idx);
|
|
|
|
/* c_lflag */
|
|
TTYSet((term.c_lflag & ISIG), WOLFSSH_ISIG, mode, &idx);
|
|
TTYSet((term.c_lflag & ICANON), WOLFSSH_ICANON, mode, &idx);
|
|
#ifdef XCASE
|
|
TTYSet((term.c_lflag & XCASE), WOLFSSH_XCASE, mode, &idx);
|
|
#endif
|
|
TTYSet((term.c_lflag & ECHO), WOLFSSH_ECHO, mode, &idx);
|
|
TTYSet((term.c_lflag & ECHOE), WOLFSSH_ECHOE, mode, &idx);
|
|
TTYSet((term.c_lflag & ECHOK), WOLFSSH_ECHOK, mode, &idx);
|
|
TTYSet((term.c_lflag & ECHONL), WOLFSSH_ECHONL, mode, &idx);
|
|
TTYSet((term.c_lflag & NOFLSH), WOLFSSH_NOFLSH, mode, &idx);
|
|
TTYSet((term.c_lflag & TOSTOP), WOLFSSH_TOSTOP, mode, &idx);
|
|
TTYSet((term.c_lflag & IEXTEN), WOLFSSH_IEXTEN, mode, &idx);
|
|
TTYSet((term.c_lflag & ECHOCTL), WOLFSSH_ECHOCTL, mode, &idx);
|
|
TTYSet((term.c_lflag & ECHOKE), WOLFSSH_ECHOKE, mode, &idx);
|
|
#ifdef PENDIN
|
|
TTYSet((term.c_lflag & PENDIN), WOLFSSH_PENDIN, mode, &idx);
|
|
#endif
|
|
|
|
/* c_oflag */
|
|
TTYSet((term.c_oflag & OPOST), WOLFSSH_OPOST, mode, &idx);
|
|
#ifdef OLCUC
|
|
TTYSet((term.c_oflag & OLCUC), WOLFSSH_OLCUC, mode, &idx);
|
|
#endif
|
|
TTYSet((term.c_oflag & ONLCR), WOLFSSH_ONLCR, mode, &idx);
|
|
TTYSet((term.c_oflag & OCRNL), WOLFSSH_OCRNL, mode, &idx);
|
|
TTYSet((term.c_oflag & ONOCR), WOLFSSH_ONOCR, mode, &idx);
|
|
TTYSet((term.c_oflag & ONLRET), WOLFSSH_ONLRET, mode, &idx);
|
|
|
|
/* c_cflag */
|
|
TTYSet((term.c_cflag & CS7), WOLFSSH_CS7, mode, &idx);
|
|
TTYSet((term.c_cflag & CS8), WOLFSSH_CS8, mode, &idx);
|
|
TTYSet((term.c_cflag & PARENB), WOLFSSH_PARENB, mode, &idx);
|
|
TTYSet((term.c_cflag & PARODD), WOLFSSH_PARODD, mode, &idx);
|
|
#endif /* !USE_WINDOWS_API && !MICROCHIP_PIC32 */
|
|
|
|
|
|
mode[idx++] = WOLFSSH_TTY_OP_OSPEED;
|
|
c32toa(buad, mode + idx); idx += UINT32_SZ;
|
|
mode[idx++] = WOLFSSH_TTY_OP_ISPEED;
|
|
c32toa(buad, mode + idx); idx += UINT32_SZ;
|
|
|
|
(void)term;
|
|
(void)ssh;
|
|
mode[idx++] = WOLFSSH_TTY_OP_END;
|
|
return idx;
|
|
}
|
|
|
|
|
|
/* sends request for pseudo-terminal (rfc 4254)
|
|
* returns WS_SUCCESS on success */
|
|
int SendChannelTerminalRequest(WOLFSSH* ssh)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
const char cType[] = "pty-req";
|
|
const char envVar[] = "xterm";
|
|
byte mode[4096];
|
|
word32 envSz, typeSz, modeSz;
|
|
word32 w = 80, h = 24;
|
|
word32 pxW = 0, pxH = 0;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelTerminalRequest()");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
envSz = (word32)WSTRLEN(envVar);
|
|
typeSz = (word32)WSTRLEN(cType);
|
|
modeSz = CreateMode(ssh, mode);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
channel = ChannelFind(ssh,
|
|
ssh->defaultPeerChannelId, WS_CHANNEL_ID_PEER);
|
|
if (channel == NULL)
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
|
|
/* craft packet with the following structure
|
|
* byte MSGID_CHANNEL_REQUEST
|
|
* word32 channel
|
|
* string "pty-req"
|
|
* boolean want_reply
|
|
* string term environment variable
|
|
* word32 terminal width
|
|
* word32 terminal height
|
|
* word32 terminal width (pixels)
|
|
* word32 terminal height (pixels)
|
|
* string encoded terminal modes
|
|
*/
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ +
|
|
typeSz + BOOLEAN_SZ +
|
|
((envSz > 0)? UINT32_SZ : 0) + envSz +
|
|
UINT32_SZ * 4 +
|
|
((modeSz > 0)? UINT32_SZ : 0) + modeSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = MSGID_CHANNEL_REQUEST;
|
|
c32toa(channel->peerChannel, output + idx); idx += UINT32_SZ;
|
|
c32toa(typeSz, output + idx); idx += LENGTH_SZ;
|
|
WMEMCPY(output + idx, cType, typeSz); idx += typeSz;
|
|
output[idx++] = 1; /* want reply */
|
|
|
|
if (envSz > 0) {
|
|
c32toa(envSz, output + idx); idx += UINT32_SZ;
|
|
WMEMCPY(output + idx, envVar, envSz); idx += envSz;
|
|
}
|
|
|
|
c32toa(w, output + idx); idx += UINT32_SZ;
|
|
c32toa(h, output + idx); idx += UINT32_SZ;
|
|
c32toa(pxW, output + idx); idx += UINT32_SZ;
|
|
c32toa(pxH, output + idx); idx += UINT32_SZ;
|
|
|
|
if (modeSz > 0) {
|
|
c32toa(modeSz, output + idx); idx += UINT32_SZ;
|
|
WMEMCPY(output + idx, mode, modeSz); idx += modeSz;
|
|
}
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
WLOG(WS_LOG_INFO, "Sending Pseudo-Terminal Channel Request: ");
|
|
WLOG(WS_LOG_INFO, " channelId = %u", channel->peerChannel);
|
|
WLOG(WS_LOG_INFO, " type = %s", cType);
|
|
WLOG(WS_LOG_INFO, " wantReply = %u", 1);
|
|
WLOG(WS_LOG_INFO, " (width , height) = (%d , %d)", w, h);
|
|
WLOG(WS_LOG_INFO, " pixels (width , height) = (%d , %d)", pxW, pxH);
|
|
WLOG(WS_LOG_INFO, " term mode = %s", mode);
|
|
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelTerminalRequest(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
#endif /* WOLFSSH_TERM */
|
|
|
|
|
|
int SendChannelSuccess(WOLFSSH* ssh, word32 channelId, int success)
|
|
{
|
|
byte* output;
|
|
word32 idx;
|
|
int ret = WS_SUCCESS;
|
|
WOLFSSH_CHANNEL* channel;
|
|
|
|
WLOG(WS_LOG_DEBUG, "Entering SendChannelSuccess(), %s",
|
|
success ? "Success" : "Failure");
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
output = ssh->outputBuffer.buffer;
|
|
idx = ssh->outputBuffer.length;
|
|
|
|
output[idx++] = success ?
|
|
MSGID_CHANNEL_SUCCESS : MSGID_CHANNEL_FAILURE;
|
|
c32toa(channel->peerChannel, output + idx);
|
|
idx += UINT32_SZ;
|
|
|
|
ssh->outputBuffer.length = idx;
|
|
|
|
ret = BundlePacket(ssh);
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
|
|
WLOG(WS_LOG_DEBUG, "Leaving SendChannelSuccess(), ret = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#if (defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)) && \
|
|
!defined(NO_WOLFSSH_SERVER)
|
|
/* cleans up absolute path */
|
|
void clean_path(char* path)
|
|
{
|
|
int i;
|
|
long sz = (long)WSTRLEN(path);
|
|
byte found;
|
|
|
|
#if defined(WOLFSSL_NUCLEUS) || defined(USE_WINDOWS_API)
|
|
for (i = 0; i < sz; i++) {
|
|
if (path[i] == '/') path[i] = '\\';
|
|
}
|
|
#endif
|
|
|
|
/* remove any ./ patterns */
|
|
for (i = 1; i < sz - 1; i++) {
|
|
if (path[i] == '.' && path[i - 1] != '.' && path[i + 1] == WS_DELIM) {
|
|
WMEMMOVE(path + i, path + i + 1, sz - i - 1);
|
|
path[sz - 1] = '\0';
|
|
i--;
|
|
}
|
|
}
|
|
sz = (int)WSTRLEN(path);
|
|
|
|
/* remove any double '/' or '\' chars */
|
|
for (i = 0; i < sz; i++) {
|
|
if ((path[i] == WS_DELIM && path[i+1] == WS_DELIM)) {
|
|
WMEMMOVE(path + i, path + i + 1, sz - i + 1);
|
|
sz -= 1;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if (path != NULL) {
|
|
/* go through path until no cases are found */
|
|
do {
|
|
int prIdx = 0; /* begin of cut */
|
|
int enIdx = 0; /* end of cut */
|
|
sz = (long)WSTRLEN(path);
|
|
|
|
found = 0;
|
|
for (i = 1; i < sz; i++) {
|
|
if (path[i] == WS_DELIM) {
|
|
int z;
|
|
|
|
/* if next two chars are .. then delete */
|
|
if (path[i+1] == '.' && path[i+2] == '.') {
|
|
enIdx = i + 3;
|
|
|
|
/* start at one char before / and retrace path */
|
|
for (z = i - 1; z > 0; z--) {
|
|
if (path[z] == WS_DELIM || path[z] == ':') {
|
|
prIdx = z;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* cut out .. and previous */
|
|
WMEMMOVE(path + prIdx, path + enIdx, sz - enIdx);
|
|
path[sz - (enIdx - prIdx)] = '\0';
|
|
|
|
if (enIdx == sz) {
|
|
path[prIdx] = '\0';
|
|
}
|
|
|
|
/* case of at / */
|
|
if (WSTRLEN(path) == 0) {
|
|
path[0] = '/';
|
|
path[1] = '\0';
|
|
}
|
|
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while (found);
|
|
|
|
#if defined(WOLFSSL_NUCLEUS) || defined(USE_WINDOWS_API)
|
|
sz = (long)WSTRLEN(path);
|
|
|
|
if (path[sz - 1] == ':') {
|
|
path[sz] = WS_DELIM;
|
|
path[sz + 1] = '\0';
|
|
}
|
|
|
|
/* clean up any multiple drive listed i.e. A:/A: */
|
|
{
|
|
int i,j;
|
|
sz = (long)WSTRLEN(path);
|
|
for (i = 0, j = 0; i < sz; i++) {
|
|
if (path[i] == ':') {
|
|
if (j == 0) j = i;
|
|
else {
|
|
/* @TODO only checking once */
|
|
WMEMMOVE(path, path + i - WS_DRIVE_SIZE,
|
|
sz - i + WS_DRIVE_SIZE);
|
|
path[sz - i + WS_DRIVE_SIZE] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* remove leading '/' for nucleus. Preserve case of single "/" */
|
|
sz = (long)WSTRLEN(path);
|
|
while (sz > 2 && path[0] == WS_DELIM) {
|
|
sz--;
|
|
WMEMMOVE(path, path + 1, sz);
|
|
path[sz] = '\0';
|
|
}
|
|
#endif
|
|
/* remove trailing delimiter */
|
|
if (sz > 3 && path[sz - 1] == WS_DELIM) {
|
|
path[sz - 1] = '\0';
|
|
}
|
|
}
|
|
}
|
|
#endif /* WOLFSSH_SFTP || WOLFSSH_SCP */
|
|
|
|
|
|
#ifdef DEBUG_WOLFSSH
|
|
|
|
#define LINE_WIDTH 16
|
|
void DumpOctetString(const byte* input, word32 inputSz)
|
|
{
|
|
int rows = inputSz / LINE_WIDTH;
|
|
int remainder = inputSz % LINE_WIDTH;
|
|
int i,j;
|
|
|
|
for (i = 0; i < rows; i++) {
|
|
printf("%04X: ", i * LINE_WIDTH);
|
|
for (j = 0; j < LINE_WIDTH; j++) {
|
|
printf("%02X ", input[i * LINE_WIDTH + j]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
if (remainder) {
|
|
printf("%04X: ", i * LINE_WIDTH);
|
|
for (j = 0; j < remainder; j++) {
|
|
printf("%02X ", input[i * LINE_WIDTH + j]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/* converts the octal input to decimal. Input is in string format i.e. 0666
|
|
* returns the decimal value on success or negative value on failure */
|
|
int wolfSSH_oct2dec(WOLFSSH* ssh, byte* oct, word32 octSz)
|
|
{
|
|
int ret;
|
|
|
|
#if defined(WOLFSSL_KEY_GEN) || defined(HAVE_COMP_KEY) || \
|
|
defined(WOLFSSL_DEBUG_MATH) || defined(DEBUG_WOLFSSL) || \
|
|
defined(WOLFSSL_PUBLIC_MP)
|
|
mp_int tmp;
|
|
char decimalString[WOLFSSH_MAX_OCTET_LEN + 1];
|
|
#else
|
|
int i;
|
|
#endif
|
|
|
|
if (octSz > WOLFSSH_MAX_OCTET_LEN || ssh == NULL || oct == NULL) {
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
#if defined(WOLFSSL_KEY_GEN) || defined(HAVE_COMP_KEY) || \
|
|
defined(WOLFSSL_DEBUG_MATH) || defined(DEBUG_WOLFSSL) || \
|
|
defined(WOLFSSL_PUBLIC_MP)
|
|
ret = mp_init(&tmp);
|
|
if (ret == MP_OKAY) {
|
|
ret = mp_read_radix(&tmp, (const char*)oct, 8);
|
|
}
|
|
|
|
if (ret == MP_OKAY) {
|
|
/* convert octal to decimal */
|
|
ret = mp_todecimal(&tmp, decimalString);
|
|
|
|
if (ret == MP_OKAY) {
|
|
/* convert string to int */
|
|
ret = atoi(decimalString);
|
|
}
|
|
}
|
|
mp_clear(&tmp);
|
|
#else
|
|
/* convert octal string to int without mp_read_radix() */
|
|
ret = 0;
|
|
|
|
for (i = 0; i < WOLFSSH_MAX_OCTET_LEN; i++)
|
|
{
|
|
if (oct[i] < '0' || oct[0] > '7') {
|
|
ret = WS_BAD_ARGUMENT;
|
|
break;
|
|
}
|
|
ret <<= 3;
|
|
ret |= (oct[i] - '0');
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* addend1 += addend2 */
|
|
void AddAssign64(word32* addend1, word32 addend2)
|
|
{
|
|
word32 tmp = addend1[0];
|
|
if ((addend1[0] += addend2) < tmp)
|
|
addend1[1]++;
|
|
}
|
|
|