wolfssh/src/internal.c

3174 lines
90 KiB
C

/* internal.c
*
* Copyright (C) 2014-2016 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 2 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*
* 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/rsa.h>
#include <wolfssl/wolfcrypt/hmac.h>
#ifdef NO_INLINE
#include <wolfssh/misc.h>
#else
#define WOLFSSH_MISC_INCLUDED
#include "src/misc.c"
#endif
const char* GetErrorString(int err)
{
(void)err;
#ifdef NO_WOLFSSH_STRINGS
return "No wolfSSH strings available";
#else
switch (err) {
case WS_SUCCESS:
return "function success";
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_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 "verify mac error";
case WS_RESOURCE_E:
return "insufficient resources for new channel";
case WS_INVALID_USERNAME:
return "invalid user name";
default:
return "Unknown error code";
}
#endif
}
typedef struct {
uint8_t 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_WOLF, "aes128-gcm@wolfssl.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" },
/* Public Key IDs */
{ ID_SSH_RSA, "ssh-rsa" },
/* UserAuth IDs */
{ ID_USERAUTH_PASSWORD, "password" },
{ ID_USERAUTH_PUBLICKEY, "publickey" },
/* Channel Type IDs */
{ ID_CHANTYPE_SESSION, "session" }
};
uint8_t NameToId(const char* name, uint32_t nameSz)
{
uint8_t id = ID_UNKNOWN;
uint32_t 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(uint8_t id)
{
const char* name = "unknown";
uint32_t i;
for (i = 0; i < (sizeof(NameIdMap)/sizeof(NameIdPair)); i++) {
if (NameIdMap[i].id == id) {
name = NameIdMap[i].name;
break;
}
}
return name;
}
static Channel* ChannelNew(WOLFSSH* ssh, uint8_t channelType,
uint32_t peerChannel,
uint32_t peerInitialWindowSz,
uint32_t peerMaxPacketSz)
{
Channel *newChannel = NULL;
WLOG(WS_LOG_DEBUG, "Entering ChannelNew()");
if (!ssh->channel.inUse) {
uint8_t* buffer = (uint8_t*)WMALLOC(DEFAULT_WINDOW_SZ,
ssh->ctx->heap, DYNTYPE_BUFFER);
if (buffer != NULL) {
newChannel = &ssh->channel;
newChannel->inUse = 1;
newChannel->channelType = channelType;
newChannel->channel = ssh->nextChannel++;
newChannel->windowSz = DEFAULT_WINDOW_SZ;
newChannel->maxPacketSz = DEFAULT_MAX_PACKET_SZ;
newChannel->peerChannel = peerChannel;
newChannel->peerWindowSz = peerInitialWindowSz;
newChannel->peerMaxPacketSz = peerMaxPacketSz;
/*
* 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 = ssh->ctx->heap;
newChannel->inputBuffer.length = 0;
newChannel->inputBuffer.idx = 0;
newChannel->inputBuffer.buffer = buffer;
newChannel->inputBuffer.bufferSz = DEFAULT_WINDOW_SZ;
newChannel->inputBuffer.dynamicFlag = 1;
}
}
WLOG(WS_LOG_INFO, "Leaving ChannelNew(), ret = %p", newChannel);
return newChannel;
}
static Channel* ChannelFind(WOLFSSH* ssh, uint32_t channel, uint8_t peer)
{
Channel* findChannel = NULL;
if (channel == (peer ? ssh->channel.peerChannel : ssh->channel.channel) &&
ssh->channel.inUse) {
findChannel = &ssh->channel;
}
return findChannel;
}
static int ChannelPutData(Channel* channel, uint8_t* data, uint32_t 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;
}
else {
return WS_RECV_OVERFLOW_E;
}
return WS_SUCCESS;
}
int BufferInit(Buffer* buffer, uint32_t 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 = (uint8_t*)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, uint32_t sz, uint32_t 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) {
uint32_t newSz = sz + usedSz;
/*WLOG(WS_LOG_DEBUG, "GB: newSz = %d", newSz);*/
if (newSz > buf->bufferSz) {
uint8_t* newBuffer = (uint8_t*)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, buf->length);
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 %s", __func__);
if (buf != NULL) {
uint32_t usedSz = buf->length - buf->idx;
WLOG(WS_LOG_DEBUG, "SB: usedSz = %u, forcedFree = %u", usedSz, forcedFree);
if (!forcedFree && usedSz > STATIC_BUFFER_LEN) {
WLOG(WS_LOG_DEBUG, "SB: shifting down");
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 %s", __func__);
}
static int Receive(WOLFSSH* ssh, uint8_t* buf, uint32_t 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)
{
int gotLine = 0;
int inSz = 255;
int in;
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;
if (ssh->inputBuffer.length > 2) {
if (ssh->inputBuffer.buffer[ssh->inputBuffer.length - 2] == '\r' &&
ssh->inputBuffer.buffer[ssh->inputBuffer.length - 1] == '\n') {
gotLine = 1;
}
}
} while (!gotLine);
return WS_SUCCESS;
}
static int SendBuffered(WOLFSSH* ssh)
{
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->ioReadCtx);
if (sent < 0) {
switch (sent) {
case WS_CBIO_ERR_WANT_WRITE: /* want write, would block */
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 ((uint32_t)sent > ssh->outputBuffer.length) {
WLOG(WS_LOG_DEBUG, "SendBuffered() out of bounds read");
return WS_SEND_OOB_READ_E;
}
ssh->outputBuffer.idx += sent;
ssh->outputBuffer.length -= sent;
}
ssh->outputBuffer.idx = 0;
WLOG(WS_LOG_DEBUG, "SB: Shrinking output buffer");
ShrinkBuffer(&ssh->outputBuffer, 0);
return WS_SUCCESS;
}
static int SendText(WOLFSSH* ssh, const char* text, uint32_t textLen)
{
GrowBuffer(&ssh->outputBuffer, textLen, 0);
WMEMCPY(ssh->outputBuffer.buffer, text, textLen);
ssh->outputBuffer.length = textLen;
return SendBuffered(ssh);
}
static int GetInputData(WOLFSSH* ssh, uint32_t 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_BUFFER_E;
/*
* 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)
return WS_MEMORY_E;
}
/* 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)
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;
} while (ssh->inputBuffer.length < size);
return 0;
}
static int DoNameList(uint8_t* idList, uint32_t* idListSz,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint8_t idListIdx;
uint32_t nameListSz, nameListIdx;
uint32_t begin = *idx;
uint8_t* name;
uint32_t nameSz;
/*
* This iterates across a name list and finds names that end in either the
* comma delimeter or with the end of the list.
*/
if (begin >= len || begin + 4 >= len)
return -1;
ato32(buf + begin, &nameListSz);
begin += 4;
if (begin + nameListSz > len)
return -1;
/* 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. */
name = buf + begin;
nameSz = 0;
nameListIdx = 0;
idListIdx = 0;
while (nameListIdx < nameListSz) {
nameListIdx++;
if (nameListIdx == nameListSz)
nameSz++;
if (nameListIdx == nameListSz || name[nameSz] == ',') {
uint8_t 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)
idList[idListIdx++] = id;
name += 1 + nameSz;
nameSz = 0;
}
else
nameSz++;
}
begin += nameListSz;
*idListSz = idListIdx;
*idx = begin;
return WS_SUCCESS;
}
static const uint8_t cannedEncAlgo[] = {ID_AES128_CBC};
static const uint8_t cannedMacAlgo[] = {ID_HMAC_SHA2_256, ID_HMAC_SHA1_96,
ID_HMAC_SHA1};
static const uint8_t cannedKeyAlgo[] = {ID_SSH_RSA};
static const uint8_t cannedKexAlgo[] = {ID_DH_GROUP14_SHA1, ID_DH_GROUP1_SHA1};
static const uint32_t cannedEncAlgoSz = sizeof(cannedEncAlgo);
static const uint32_t cannedMacAlgoSz = sizeof(cannedMacAlgo);
static const uint32_t cannedKeyAlgoSz = sizeof(cannedKeyAlgo);
static const uint32_t cannedKexAlgoSz = sizeof(cannedKexAlgo);
static uint8_t MatchIdLists(const uint8_t* left, uint32_t leftSz,
const uint8_t* right, uint32_t rightSz)
{
uint32_t i, j;
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 uint8_t BlockSzForId(uint8_t id)
{
switch (id) {
case ID_AES128_CBC:
case ID_AES128_CTR:
return AES_BLOCK_SIZE;
default:
return 0;
}
}
static INLINE uint8_t MacSzForId(uint8_t 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 uint8_t KeySzForId(uint8_t 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:
return AES_BLOCK_SIZE;
default:
return 0;
}
}
static int DoKexInit(WOLFSSH* ssh, uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint8_t algoId;
uint8_t list[3];
uint32_t listSz;
uint32_t skipSz;
uint32_t begin = *idx;
/*
* 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.
*/
/* Check that the cookie exists inside the message */
if (begin + COOKIE_SZ > len) {
/* error, out of bounds */
return WS_FATAL_ERROR;
}
/* Move past the cookie. */
begin += COOKIE_SZ;
/* KEX Algorithms */
WLOG(WS_LOG_DEBUG, "DKI: KEX Algorithms");
listSz = 2;
DoNameList(list, &listSz, buf, len, &begin);
algoId = MatchIdLists(list, listSz, cannedKexAlgo, cannedKexAlgoSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate KEX Algo");
return WS_INVALID_ALGO_ID;
}
ssh->handshake->kexId = algoId;
/* Server Host Key Algorithms */
WLOG(WS_LOG_DEBUG, "DKI: Server Host Key Algorithms");
listSz = 1;
DoNameList(list, &listSz, buf, len, &begin);
algoId = MatchIdLists(list, listSz, cannedKeyAlgo, cannedKeyAlgoSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Server Host Key Algo");
return WS_INVALID_ALGO_ID;
}
ssh->handshake->pubKeyId = algoId;
/* Enc Algorithms - Client to Server */
WLOG(WS_LOG_DEBUG, "DKI: Enc Algorithms - Client to Server");
listSz = 3;
DoNameList(list, &listSz, buf, len, &begin);
algoId = MatchIdLists(list, listSz, cannedEncAlgo, cannedEncAlgoSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo C2S");
return WS_INVALID_ALGO_ID;
}
/* Enc Algorithms - Server to Client */
WLOG(WS_LOG_DEBUG, "DKI: Enc Algorithms - Server to Client");
listSz = 3;
DoNameList(list, &listSz, buf, len, &begin);
if (MatchIdLists(list, listSz, &algoId, 1) == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo S2C");
return WS_INVALID_ALGO_ID;
}
ssh->handshake->encryptId = algoId;
ssh->handshake->blockSz = ssh->ivClientSz = ssh->ivServerSz
= BlockSzForId(algoId);
ssh->encKeyClientSz = ssh->encKeyServerSz = KeySzForId(algoId);
/* MAC Algorithms - Client to Server */
WLOG(WS_LOG_DEBUG, "DKI: MAC Algorithms - Client to Server");
listSz = 2;
DoNameList(list, &listSz, buf, len, &begin);
algoId = MatchIdLists(list, listSz, cannedMacAlgo, cannedMacAlgoSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo C2S");
return WS_INVALID_ALGO_ID;
}
/* MAC Algorithms - Server to Client */
WLOG(WS_LOG_DEBUG, "DKI: MAC Algorithms - Server to Client");
listSz = 2;
DoNameList(list, &listSz, buf, len, &begin);
if (MatchIdLists(list, listSz, &algoId, 1) == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
return WS_INVALID_ALGO_ID;
}
ssh->handshake->macId = algoId;
ssh->handshake->macSz = MacSzForId(algoId);
ssh->macKeyClientSz = ssh->macKeyServerSz = KeySzForId(algoId);
/* The compression algorithm lists should have none as a value. */
algoId = ID_NONE;
/* Compression Algorithms - Client to Server */
WLOG(WS_LOG_DEBUG, "DKI: Compression Algorithms - Client to Server");
listSz = 1;
DoNameList(list, &listSz, buf, len, &begin);
if (MatchIdLists(list, listSz, &algoId, 1) == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Compression Algo C2S");
return WS_INVALID_ALGO_ID;
}
/* Compression Algorithms - Server to Client */
WLOG(WS_LOG_DEBUG, "DKI: Compression Algorithms - Server to Client");
listSz = 1;
DoNameList(list, &listSz, buf, len, &begin);
if (MatchIdLists(list, listSz, &algoId, 1) == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Compression Algo S2C");
return WS_INVALID_ALGO_ID;
}
/* Languages - Client to Server, skip */
ato32(buf + begin, &skipSz);
begin += 4 + skipSz;
/* Languages - Server to Client, skip */
ato32(buf + begin, &skipSz);
begin += 4 + skipSz;
/* First KEX Packet Follows */
ssh->handshake->kexPacketFollows = buf[begin];
begin += 1;
/* Skip the "for future use" length. */
ato32(buf + begin, &skipSz);
begin += 4 + skipSz;
*idx = begin;
ssh->clientState = CLIENT_KEXINIT_DONE;
return WS_SUCCESS;
}
static const uint8_t dhGenerator[] = { 2 };
static const uint8_t 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 uint8_t 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 uint32_t dhGeneratorSz = sizeof(dhGenerator);
static const uint32_t dhPrimeGroup1Sz = sizeof(dhPrimeGroup1);
static const uint32_t dhPrimeGroup14Sz = sizeof(dhPrimeGroup14);
static int DoKexDhInit(WOLFSSH* ssh, uint8_t* buf, uint32_t len, uint32_t* 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. */
/* DYNTYPE_DH */
uint8_t* e;
uint32_t eSz;
uint32_t begin = *idx;
(void)len;
ato32(buf + begin, &eSz);
begin += LENGTH_SZ;
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;
return WS_SUCCESS;
}
static int DoNewKeys(WOLFSSH* ssh, uint8_t* buf, uint32_t len, uint32_t* idx)
{
(void)buf;
(void)len;
(void)idx;
ssh->peerEncryptId = ssh->handshake->encryptId;
ssh->peerMacId = ssh->handshake->macId;
ssh->peerBlockSz = ssh->handshake->blockSz;
ssh->peerMacSz = ssh->handshake->macSz;
switch (ssh->peerEncryptId) {
case ID_NONE:
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher none");
break;
case ID_AES128_CBC:
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher aes128-cbc");
wc_AesSetKey(&ssh->decryptCipher.aes,
ssh->encKeyClient, ssh->encKeyClientSz,
ssh->ivClient, AES_DECRYPTION);
break;
default:
WLOG(WS_LOG_DEBUG, "DNK: peer using cipher invalid");
break;
}
ssh->rxCount = 0;
ssh->clientState = CLIENT_USING_KEYS;
return WS_SUCCESS;
}
int GenerateKey(uint8_t hashId, uint8_t keyId,
uint8_t* key, uint32_t keySz,
const uint8_t* k, uint32_t kSz,
const uint8_t* h, uint32_t hSz,
const uint8_t* sessionId, uint32_t sessionIdSz)
{
uint32_t blocks, remainder;
Sha sha;
uint8_t kPad = 0;
uint8_t pad = 0;
uint8_t kSzFlat[LENGTH_SZ];
(void)hashId;
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;
}
if (k[0] & 0x80) kPad = 1;
c32toa(kSz + kPad, kSzFlat);
blocks = keySz / SHA_DIGEST_SIZE;
remainder = keySz % SHA_DIGEST_SIZE;
wc_InitSha(&sha);
wc_ShaUpdate(&sha, kSzFlat, LENGTH_SZ);
if (kPad) wc_ShaUpdate(&sha, &pad, 1);
wc_ShaUpdate(&sha, k, kSz);
wc_ShaUpdate(&sha, h, hSz);
wc_ShaUpdate(&sha, &keyId, sizeof(keyId));
wc_ShaUpdate(&sha, sessionId, sessionIdSz);
if (blocks == 0) {
if (remainder > 0) {
uint8_t lastBlock[SHA_DIGEST_SIZE];
wc_ShaFinal(&sha, lastBlock);
WMEMCPY(key, lastBlock, remainder);
}
}
else {
uint32_t runningKeySz, curBlock;
wc_ShaFinal(&sha, key);
runningKeySz = SHA_DIGEST_SIZE;
for (curBlock = 1; curBlock < blocks; curBlock++) {
wc_InitSha(&sha);
wc_ShaUpdate(&sha, kSzFlat, LENGTH_SZ);
if (kPad) wc_ShaUpdate(&sha, &pad, 1);
wc_ShaUpdate(&sha, k, kSz);
wc_ShaUpdate(&sha, h, hSz);
wc_ShaUpdate(&sha, key, runningKeySz);
wc_ShaFinal(&sha, key + runningKeySz);
runningKeySz += SHA_DIGEST_SIZE;
}
if (remainder > 0) {
uint8_t lastBlock[SHA_DIGEST_SIZE];
wc_InitSha(&sha);
wc_ShaUpdate(&sha, kSzFlat, LENGTH_SZ);
if (kPad) wc_ShaUpdate(&sha, &pad, 1);
wc_ShaUpdate(&sha, k, kSz);
wc_ShaUpdate(&sha, h, hSz);
wc_ShaUpdate(&sha, key, runningKeySz);
wc_ShaFinal(&sha, lastBlock);
WMEMCPY(key + runningKeySz, lastBlock, remainder);
}
}
return 0;
}
static int GenerateKeys(WOLFSSH* ssh)
{
if (ssh == NULL)
return WS_BAD_ARGUMENT;
GenerateKey(0, 'A',
ssh->ivClient, ssh->ivClientSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
GenerateKey(0, 'B',
ssh->ivServer, ssh->ivServerSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
GenerateKey(0, 'C',
ssh->encKeyClient, ssh->encKeyClientSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
GenerateKey(0, 'D',
ssh->encKeyServer, ssh->encKeyServerSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
GenerateKey(0, 'E',
ssh->macKeyClient, ssh->macKeyClientSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
GenerateKey(0, 'F',
ssh->macKeyServer, ssh->macKeyServerSz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz);
#ifdef SHOW_SECRETS
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(ssh->ivClient, ssh->ivClientSz);
printf("B:\n");
DumpOctetString(ssh->ivServer, ssh->ivServerSz);
printf("C:\n");
DumpOctetString(ssh->encKeyClient, ssh->encKeyClientSz);
printf("D:\n");
DumpOctetString(ssh->encKeyServer, ssh->encKeyServerSz);
printf("E:\n");
DumpOctetString(ssh->macKeyClient, ssh->macKeyClientSz);
printf("F:\n");
DumpOctetString(ssh->macKeyServer, ssh->macKeyServerSz);
printf("\n");
#endif /* SHOW_SECRETS */
return 0;
}
static int DoIgnore(WOLFSSH* ssh, uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t dataSz;
uint32_t begin = *idx;
(void)ssh;
(void)len;
ato32(buf + begin, &dataSz);
begin += LENGTH_SZ + dataSz;
*idx = begin;
return WS_SUCCESS;
}
static int DoDebug(WOLFSSH* ssh, uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint8_t alwaysDisplay;
char* msg = NULL;
char* lang = NULL;
uint32_t strSz;
uint32_t begin = *idx;
(void)ssh;
(void)len;
alwaysDisplay = buf[begin++];
ato32(buf + begin, &strSz);
begin += LENGTH_SZ;
if (strSz > 0) {
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) {
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,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t seq;
uint32_t 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, uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t reason;
const char* reasonStr;
uint32_t 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;
}
#if 0
static const char serviceNameUserAuth[] = "ssh-userauth";
static const char serviceNameConnection[] = "ssh-connection";
#endif
static int DoServiceRequest(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
uint32_t nameSz;
char serviceName[32];
(void)len;
ato32(buf + begin, &nameSz);
begin += LENGTH_SZ;
XMEMCPY(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 GetBoolean(uint8_t* v, uint8_t* buf, uint32_t len, uint32_t* idx)
{
int result = WS_BUFFER_E;
if (*idx < len) {
*v = buf[*idx];
*idx += BOOLEAN_SZ;
result = WS_SUCCESS;
}
return result;
}
static int GetUint32(uint32_t* v, uint8_t* buf, uint32_t len, uint32_t* idx)
{
int result = WS_BUFFER_E;
if (*idx < len && *idx + UINT32_SZ <= len) {
ato32(buf + *idx, v);
*idx += UINT32_SZ;
result = WS_SUCCESS;
}
return result;
}
static int GetString(char* s, uint32_t* sSz,
uint8_t* buf, uint32_t len, uint32_t *idx)
{
int result;
result = GetUint32(sSz, buf, len, idx);
if (result == WS_SUCCESS) {
result = WS_BUFFER_E;
if (*idx < len && *idx + *sSz <= len) {
XMEMCPY(s, buf + *idx, *sSz);
*idx += *sSz;
s[*sSz] = 0;
result = WS_SUCCESS;
}
}
return result;
}
/* Utility for DoUserAuthRequest() */
static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
WS_UserAuthData_Password* pw = &authData->sf.password;
int ret;
authData->type = WOLFSSH_USERAUTH_PASSWORD;
ret = GetBoolean(&pw->hasNewPassword, buf, len, &begin);
ret = GetUint32(&pw->passwordSz, buf, len, &begin);
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);
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;
}
else {
WLOG(WS_LOG_DEBUG, "DUARPW: password check failed");
if (ret != WOLFSSH_USERAUTH_SUCCESS) {
switch (ret) {
case WOLFSSH_USERAUTH_INVALID_USER:
SendDisconnect(ssh,
WOLFSSH_DISCONNECT_ILLEGAL_USER_NAME);
break;
default:
SendUserAuthFailure(ssh, 0);
}
}
}
}
else {
WLOG(WS_LOG_DEBUG, "DUARPW: No user auth callback");
SendUserAuthFailure(ssh, 0);
}
*idx = begin;
return WS_SUCCESS;
}
/* Utility for DoUserAuthRequestPublicKey() */
/* returns negative for error, positive is size of digest. */
static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk,
uint8_t* digest, uint32_t digestSz)
{
RsaKey key;
uint8_t* publicKeyType;
uint32_t publicKeyTypeSz;
uint8_t* n;
uint32_t nSz;
uint8_t* e;
uint32_t eSz;
uint32_t i = 0;
int ret;
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestRsa()");
/* First check that the public key's type matches the one we are
* expecting. */
GetUint32(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i);
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");
return WS_INVALID_ALGO_ID;
}
GetUint32(&eSz, pk->publicKey, pk->publicKeySz, &i);
e = pk->publicKey + i;
i += eSz;
GetUint32(&nSz, pk->publicKey, pk->publicKeySz, &i);
n = pk->publicKey + i;
wc_InitRsaKey(&key, ssh->ctx->heap);
ret = wc_RsaPublicKeyDecodeRaw(n, nSz, e, eSz, &key);
i = 0;
/* First check that the signature's public key type matches the one
* we are expecting. */
GetUint32(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i);
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");
return WS_INVALID_ALGO_ID;
}
GetUint32(&nSz, pk->signature, pk->signatureSz, &i);
n = pk->signature + i;
ret = wc_RsaSSL_Verify(n, nSz, digest, digestSz, &key);
wc_FreeRsaKey(&key);
return ret;
}
/* Utility for DoUserAuthRequest() */
static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
WS_UserAuthData_PublicKey* pk = &authData->sf.publicKey;
int ret = WS_SUCCESS;
WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestPublicKey()");
DumpOctetString(buf + begin, len - begin);
authData->type = WOLFSSH_USERAUTH_PUBLICKEY;
GetBoolean(&pk->hasSignature, buf, len, &begin);
GetUint32(&pk->publicKeyTypeSz, buf, len, &begin);
pk->publicKeyType = buf + begin;
begin += pk->publicKeyTypeSz;
GetUint32(&pk->publicKeySz, buf, len, &begin);
pk->publicKey = buf + begin;
begin += pk->publicKeySz;
if (pk->hasSignature) {
GetUint32(&pk->signatureSz, buf, len, &begin);
pk->signature = buf + begin;
begin += pk->signatureSz;
}
else {
pk->signature = NULL;
pk->signatureSz = 0;
}
*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) {
switch (ret) {
case WOLFSSH_USERAUTH_INVALID_USER:
return SendDisconnect(ssh,
WOLFSSH_DISCONNECT_ILLEGAL_USER_NAME);
default:
return SendUserAuthFailure(ssh, 0);
/* XXX Need to tell User Auth layer to disallow
* public key user auth */
}
}
}
else {
WLOG(WS_LOG_DEBUG, "DUARPK: no userauth callback set");
}
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 {
uint8_t checkDigest[MAX_ENCODED_SIG_SZ];
uint32_t checkDigestSz = sizeof(checkDigest);
uint8_t encDigest[MAX_ENCODED_SIG_SZ];
uint32_t encDigestSz;
uint8_t pkTypeId;
pkTypeId = NameToId((char*)pk->publicKeyType, pk->publicKeyTypeSz);
if (pkTypeId == ID_SSH_RSA)
ret = DoUserAuthRequestRsa(ssh, pk, checkDigest, checkDigestSz);
else
ret = WS_INVALID_ALGO_ID;
if (ret > 0) {
checkDigestSz = (uint32_t)ret;
ret = WS_SUCCESS;
}
{
Sha sha;
uint8_t digest[SHA_DIGEST_SIZE];
wc_InitSha(&sha);
c32toa(ssh->sessionIdSz, digest);
wc_ShaUpdate(&sha, digest, UINT32_SZ);
wc_ShaUpdate(&sha, ssh->sessionId, ssh->sessionIdSz);
digest[0] = MSGID_USERAUTH_REQUEST;
wc_ShaUpdate(&sha, digest, MSG_ID_SZ);
/* The rest of the fields in the signature are already
* in the buffer. Just need to account for the sizes. */
wc_ShaUpdate(&sha, pk->dataToSign,
authData->usernameSz + authData->serviceNameSz +
authData->authNameSz + BOOLEAN_SZ +
pk->publicKeyTypeSz + pk->publicKeySz +
(UINT32_SZ * 5));
wc_ShaFinal(&sha, digest);
encDigestSz = wc_EncodeSignature(encDigest, digest,
SHA_DIGEST_SIZE, SHAh);
}
{
volatile int compare;
volatile int sizeCompare;
compare = ConstantCompare(encDigest, checkDigest, encDigestSz);
sizeCompare = encDigestSz != checkDigestSz;
if (compare || sizeCompare || ret < 0) {
WLOG(WS_LOG_DEBUG, "DUARPK: signature compare failure");
SendUserAuthFailure(ssh, 0);
}
else {
ssh->clientState = CLIENT_USERAUTH_DONE;
}
}
}
return ret;
}
static int DoUserAuthRequest(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
int ret;
uint8_t authNameId;
WS_UserAuthData authData;
GetUint32(&authData.usernameSz, buf, len, &begin);
authData.username = buf + begin;
begin += authData.usernameSz;
GetUint32(&authData.serviceNameSz, buf, len, &begin);
authData.serviceName = buf + begin;
begin += authData.serviceNameSz;
GetUint32(&authData.authNameSz, buf, len, &begin);
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);
}
else {
WLOG(WS_LOG_DEBUG, "invalid userauth type: %s", IdToName(authNameId));
ret = SendUserAuthFailure(ssh, 0);
}
*idx = begin;
return ret;
}
static int DoChannelOpen(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
uint32_t typeSz;
char type[32];
uint8_t typeId;
uint32_t peerChannel;
uint32_t peerInitialWindowSz;
uint32_t peerMaxPacketSz;
int ret;
Channel* newChannel;
WLOG(WS_LOG_DEBUG, "Entering DoChannelOpen()");
ret = GetString(type, &typeSz, buf, len, &begin);
if (ret == WS_SUCCESS)
ret = GetUint32(&peerChannel, 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_DEBUG, "Leaving DoChannelOpen(), ret = %d", ret);
return ret;
}
WLOG(WS_LOG_INFO, " type = %s", type);
WLOG(WS_LOG_INFO, " peerChannel = %u", peerChannel);
WLOG(WS_LOG_INFO, " peerInitialWindowSz = %u", peerInitialWindowSz);
WLOG(WS_LOG_INFO, " peerMaxPacketSz = %u", peerMaxPacketSz);
*idx = begin;
typeId = NameToId(type, typeSz);
if (typeId != ID_CHANTYPE_SESSION)
return WS_INVALID_CHANTYPE;
newChannel = ChannelNew(ssh, typeId,
peerChannel, peerInitialWindowSz, peerMaxPacketSz);
if (newChannel == NULL)
return WS_RESOURCE_E;
ssh->clientState = CLIENT_DONE;
newChannel->peerChannel = peerChannel;
newChannel->peerWindowSz = peerInitialWindowSz;
newChannel->peerMaxPacketSz = peerMaxPacketSz;
return WS_SUCCESS;
}
static int DoChannelRequest(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
uint32_t channel;
uint32_t typeSz;
char type[32];
uint8_t wantReply;
int ret;
WLOG(WS_LOG_DEBUG, "Entering DoChannelRequest()");
(void)ssh;
ret = GetUint32(&channel, buf, len, &begin);
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;
}
WLOG(WS_LOG_DEBUG, " channel = %u", channel);
WLOG(WS_LOG_DEBUG, " type = %s", type);
WLOG(WS_LOG_DEBUG, " wantReply = %u", wantReply);
if (WSTRNCMP(type, "pty-req", typeSz) == 0) {
char term[32];
uint32_t termSz;
uint32_t widthChar, heightRows, widthPixels, heightPixels;
uint32_t modesSz;
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, "Leaving DoChannelRequest(), ret = %d", ret);
return ret;
}
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 if (WSTRNCMP(type, "env", typeSz) == 0) {
char name[32];
uint32_t nameSz;
char value[32];
uint32_t valueSz;
ret = GetString(name, &nameSz, buf, len, &begin);
if (ret == WS_SUCCESS)
ret = GetString(value, &valueSz, buf, len, &begin);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Leaving DoChannelRequest(), ret = %d", ret);
return ret;
}
WLOG(WS_LOG_DEBUG, " %s = %s", name, value);
}
*idx = len;
WLOG(WS_LOG_DEBUG, "Leaving DoChannelRequest()");
return WS_SUCCESS;
}
static int DoChannelWindowAdjust(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
uint32_t channel, bytesToAdd;
int ret;
WLOG(WS_LOG_DEBUG, "Entering DoChannelWindowAdjust()");
ret = GetUint32(&channel, buf, len, &begin);
if (ret == WS_SUCCESS)
ret = GetUint32(&bytesToAdd, buf, len, &begin);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Leaving DoChannelWindowAdjust(), ret = %d", ret);
return ret;
}
WLOG(WS_LOG_INFO, " channel = %u", channel);
WLOG(WS_LOG_INFO, " bytesToAdd = %u", bytesToAdd);
WLOG(WS_LOG_INFO, " peerWindowSz = %u", ssh->channel.peerWindowSz);
ssh->channel.peerWindowSz += bytesToAdd;
WLOG(WS_LOG_INFO, " update peerWindowSz = %u", ssh->channel.peerWindowSz);
WLOG(WS_LOG_DEBUG, "Leaving DoChannelWindowAdjust()");
return WS_SUCCESS;
}
static int DoChannelData(WOLFSSH* ssh,
uint8_t* buf, uint32_t len, uint32_t* idx)
{
uint32_t begin = *idx;
uint32_t channel, dataSz;
int ret;
Channel* chan = NULL;
(void)ssh;
WLOG(WS_LOG_DEBUG, "Entering DoChannelData()");
ret = GetUint32(&channel, buf, len, &begin);
if (ret == WS_SUCCESS)
ret = GetUint32(&dataSz, buf, len, &begin);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Leaving DoChannelData(), ret = %d", ret);
return ret;
}
chan = ChannelFind(ssh, channel, 0);
if (chan != NULL) {
ret = ChannelPutData(chan, buf + begin, dataSz);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Leaving DoChannelData(), ret = %d", ret);
return ret;
}
}
*idx = begin + dataSz;
WLOG(WS_LOG_DEBUG, "Leaving DoChannelData()");
return WS_SUCCESS;
}
static int DoPacket(WOLFSSH* ssh)
{
uint8_t* buf = (uint8_t*)ssh->inputBuffer.buffer;
uint32_t idx = ssh->inputBuffer.idx;
uint32_t len = ssh->inputBuffer.length;
uint32_t payloadSz;
uint8_t padSz;
uint8_t msg;
uint32_t payloadIdx = 0;
WLOG(WS_LOG_DEBUG, "DoPacket sequence number: %d", ssh->peerSeq);
idx += LENGTH_SZ;
padSz = buf[idx++];
payloadSz = ssh->curSz - PAD_LENGTH_SZ - padSz - MSG_ID_SZ;
msg = buf[idx++];
/* At this point, payload starts at "buf + idx". */
switch (msg) {
case MSGID_DISCONNECT:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
DoDisconnect(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_IGNORE:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
DoIgnore(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_UNIMPLEMENTED:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
DoUnimplemented(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_DEBUG:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
DoDebug(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_KEXINIT:
{
uint8_t scratchLen[LENGTH_SZ];
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXINIT");
c32toa(payloadSz + sizeof(msg), scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
wc_ShaUpdate(&ssh->handshake->hash, &msg, sizeof(msg));
wc_ShaUpdate(&ssh->handshake->hash, buf + idx, payloadSz);
DoKexInit(ssh, buf + idx, payloadSz, &payloadIdx);
}
break;
case MSGID_NEWKEYS:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_NEWKEYS");
DoNewKeys(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_KEXDH_INIT:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_KEXDH_INIT");
DoKexDhInit(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_SERVICE_REQUEST:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_SERVICE_REQUEST");
DoServiceRequest(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_USERAUTH_REQUEST:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_REQUEST");
DoUserAuthRequest(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_CHANNEL_OPEN:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_OPEN");
DoChannelOpen(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_CHANNEL_WINDOW_ADJUST:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_WINDOW_ADJUST");
DoChannelWindowAdjust(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_CHANNEL_DATA:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_DATA");
DoChannelData(ssh, buf + idx, payloadSz, &payloadIdx);
break;
case MSGID_CHANNEL_REQUEST:
WLOG(WS_LOG_DEBUG, "Decoding MSGID_CHANNEL_REQUEST");
DoChannelRequest(ssh, buf + idx, payloadSz, &payloadIdx);
break;
default:
WLOG(WS_LOG_DEBUG, "Unimplemented message ID (%d)", msg);
#ifdef SHOW_UNIMPLEMENTED
DumpOctetString(buf + idx, payloadSz);
#endif
SendUnimplemented(ssh);
break;
}
idx += payloadIdx;
if (idx + padSz > len) {
WLOG(WS_LOG_DEBUG, "Not enough data in buffer for pad.");
return WS_BUFFER_E;
}
idx += padSz;
ssh->inputBuffer.idx = idx;
ssh->peerSeq++;
return WS_SUCCESS;
}
static INLINE int Encrypt(WOLFSSH* ssh, uint8_t* cipher, const uint8_t* input,
uint16_t 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;
case ID_AES128_CBC:
if (wc_AesCbcEncrypt(&ssh->encryptCipher.aes,
cipher, input, sz) < 0) {
ret = WS_ENCRYPT_E;
}
break;
default:
ret = WS_INVALID_ALGO_ID;
}
ssh->txCount += sz;
return ret;
}
static INLINE int Decrypt(WOLFSSH* ssh, uint8_t* plain, const uint8_t* input,
uint16_t 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;
case ID_AES128_CBC:
if (wc_AesCbcDecrypt(&ssh->decryptCipher.aes,
plain, input, sz) < 0) {
ret = WS_DECRYPT_E;
}
break;
default:
ret = WS_INVALID_ALGO_ID;
}
ssh->rxCount += sz;
return ret;
}
static INLINE int CreateMac(WOLFSSH* ssh, const uint8_t* in, uint32_t inSz,
uint8_t* mac)
{
uint8_t flatSeq[LENGTH_SZ];
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:
break;
case ID_HMAC_SHA1_96:
{
Hmac hmac;
uint8_t digest[SHA_DIGEST_SIZE];
wc_HmacSetKey(&hmac, SHA,
ssh->macKeyServer, ssh->macKeyServerSz);
wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
wc_HmacUpdate(&hmac, in, inSz);
wc_HmacFinal(&hmac, digest);
WMEMCPY(mac, digest, SHA1_96_SZ);
}
break;
case ID_HMAC_SHA1:
{
Hmac hmac;
wc_HmacSetKey(&hmac, SHA,
ssh->macKeyServer, ssh->macKeyServerSz);
wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
wc_HmacUpdate(&hmac, in, inSz);
wc_HmacFinal(&hmac, mac);
}
break;
case ID_HMAC_SHA2_256:
{
Hmac hmac;
wc_HmacSetKey(&hmac, SHA256,
ssh->macKeyServer, ssh->macKeyServerSz);
wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
wc_HmacUpdate(&hmac, in, inSz);
wc_HmacFinal(&hmac, mac);
}
break;
default:
WLOG(WS_LOG_DEBUG, "Invalid Mac ID");
return WS_FATAL_ERROR;
}
return WS_SUCCESS;
}
static INLINE int VerifyMac(WOLFSSH* ssh, const uint8_t* in, uint32_t inSz,
const uint8_t* mac)
{
int ret = WS_SUCCESS;
uint8_t flatSeq[LENGTH_SZ];
uint8_t 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->macKeyClientSz);
switch (ssh->peerMacId) {
case ID_NONE:
break;
case ID_HMAC_SHA1:
case ID_HMAC_SHA1_96:
wc_HmacSetKey(&hmac, SHA, ssh->macKeyClient, ssh->macKeyClientSz);
wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
wc_HmacUpdate(&hmac, in, inSz);
wc_HmacFinal(&hmac, checkMac);
if (ConstantCompare(checkMac, mac, ssh->peerMacSz) != 0)
ret = WS_VERIFY_MAC_E;
break;
case ID_HMAC_SHA2_256:
wc_HmacSetKey(&hmac, SHA256,
ssh->macKeyClient, ssh->macKeyClientSz);
wc_HmacUpdate(&hmac, flatSeq, sizeof(flatSeq));
wc_HmacUpdate(&hmac, in, inSz);
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;
}
int ProcessReply(WOLFSSH* ssh)
{
int ret = WS_FATAL_ERROR;
int verifyResult;
uint32_t readSz;
uint8_t peerBlockSz = ssh->peerBlockSz;
uint8_t peerMacSz = ssh->peerMacSz;
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;
/* 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");
return ret;
}
case PROCESS_PACKET_LENGTH:
/* Peek at the packet_length field. */
ato32(ssh->inputBuffer.buffer + ssh->inputBuffer.idx,
&ssh->curSz);
ssh->processReplyState = PROCESS_PACKET_FINISH;
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;
}
ret = Decrypt(ssh,
ssh->inputBuffer.buffer +
ssh->inputBuffer.idx + peerBlockSz,
ssh->inputBuffer.buffer +
ssh->inputBuffer.idx + peerBlockSz,
ssh->curSz + LENGTH_SZ - peerBlockSz);
/* 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");
return ret;
}
if (verifyResult != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "PR: VerifyMac fail");
return ret;
}
}
ssh->processReplyState = PROCESS_PACKET;
case PROCESS_PACKET:
if ( (ret = DoPacket(ssh)) < 0) {
return ret;
}
WLOG(WS_LOG_DEBUG, "PR3: peerMacSz = %u", peerMacSz);
ssh->inputBuffer.idx += peerMacSz;
break;
default:
WLOG(WS_LOG_DEBUG, "Bad process input state, program error");
return WS_INPUT_CASE_E;
}
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;
}
}
static const char sshIdStr[] = "SSH-2.0-wolfSSHv"
LIBWOLFSSH_VERSION_STRING
"\r\n";
int ProcessClientVersion(WOLFSSH* ssh)
{
int error;
uint32_t protoLen = 7; /* Length of the SSH-2.0 portion of the ID str */
uint8_t scratch[LENGTH_SZ];
if ( (error = GetInputText(ssh)) < 0) {
WLOG(WS_LOG_DEBUG, "get input text failed");
return error;
}
if (WSTRNCASECMP((char*)ssh->inputBuffer.buffer, sshIdStr, protoLen) == 0) {
ssh->clientState = CLIENT_VERSION_DONE;
}
else {
WLOG(WS_LOG_DEBUG, "SSH version mismatch");
return WS_VERSION_E;
}
c32toa(ssh->inputBuffer.length - 2, scratch);
wc_ShaUpdate(&ssh->handshake->hash, scratch, LENGTH_SZ);
wc_ShaUpdate(&ssh->handshake->hash, ssh->inputBuffer.buffer,
ssh->inputBuffer.length - 2);
ssh->inputBuffer.idx += ssh->inputBuffer.length;
return WS_SUCCESS;
}
int SendServerVersion(WOLFSSH* ssh)
{
uint32_t sshIdStrSz = (uint32_t)WSTRLEN(sshIdStr);
uint8_t scratch[LENGTH_SZ];
WLOG(WS_LOG_DEBUG, "%s", sshIdStr);
SendText(ssh, sshIdStr, (uint32_t)WSTRLEN(sshIdStr));
sshIdStrSz -= 2; /* Remove the CRLF */
c32toa(sshIdStrSz, scratch);
wc_ShaUpdate(&ssh->handshake->hash, scratch, LENGTH_SZ);
wc_ShaUpdate(&ssh->handshake->hash, (const uint8_t*)sshIdStr, sshIdStrSz);
return WS_SUCCESS;
}
static int PreparePacket(WOLFSSH* ssh, uint32_t payloadSz)
{
int ret;
uint8_t* output;
uint32_t outputSz;
uint32_t packetSz;
uint32_t usedSz;
uint8_t paddingSz;
/* Minimum value for paddingSz is 4. */
paddingSz = ssh->blockSz -
(LENGTH_SZ + PAD_LENGTH_SZ + payloadSz) % ssh->blockSz;
if (paddingSz < 4)
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;
if ( (ret = GrowBuffer(&ssh->outputBuffer, outputSz, usedSz)) != WS_SUCCESS)
return ret;
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)
{
uint8_t* output;
uint32_t idx;
uint8_t paddingSz;
int 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
ret = wc_RNG_GenerateBlock(ssh->rng, output + idx, paddingSz);
idx += paddingSz;
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "BP: failed to add padding");
return ret;
}
ret = CreateMac(ssh, ssh->outputBuffer.buffer + ssh->packetStartIdx,
ssh->outputBuffer.length - ssh->packetStartIdx + paddingSz,
output + idx);
idx += ssh->macSz;
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "BP: failed to add mac");
return ret;
}
ret = Encrypt(ssh,
ssh->outputBuffer.buffer + ssh->packetStartIdx,
ssh->outputBuffer.buffer + ssh->packetStartIdx,
ssh->outputBuffer.length - ssh->packetStartIdx + paddingSz);
ssh->outputBuffer.length = idx;
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "BP: failed to encrypt buffer");
return ret;
}
return WS_SUCCESS;
}
static INLINE void CopyNameList(uint8_t* buf, uint32_t* idx,
const char* src, uint32_t srcSz)
{
uint32_t begin = *idx;
c32toa(srcSz, buf + begin);
begin += LENGTH_SZ;
WMEMCPY(buf + begin, src, srcSz);
begin += srcSz;
*idx = begin;
}
static const char cannedEncAlgoNames[] = "aes128-cbc";
static const char cannedMacAlgoNames[] = "hmac-sha2-256,hmac-sha1-96,"
"hmac-sha1";
static const char cannedKeyAlgoNames[] = "ssh-rsa";
static const char cannedKexAlgoNames[] = "diffie-hellman-group14-sha1,"
"diffie-hellman-group1-sha1";
static const char cannedNoneNames[] = "none";
static const uint32_t cannedEncAlgoNamesSz = sizeof(cannedEncAlgoNames) - 1;
static const uint32_t cannedMacAlgoNamesSz = sizeof(cannedMacAlgoNames) - 1;
static const uint32_t cannedKeyAlgoNamesSz = sizeof(cannedKeyAlgoNames) - 1;
static const uint32_t cannedKexAlgoNamesSz = sizeof(cannedKexAlgoNames) - 1;
static const uint32_t cannedNoneNamesSz = sizeof(cannedNoneNames) - 1;
int SendKexInit(WOLFSSH* ssh)
{
uint8_t* output;
uint8_t* payload;
uint32_t idx = 0;
uint32_t payloadSz;
int ret = WS_SUCCESS;
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)
return ret;
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);
idx += COOKIE_SZ;
if (ret != 0)
return ret;
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;
{
uint8_t scratchLen[LENGTH_SZ];
c32toa(payloadSz, scratchLen);
ret = wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
if (ret != 0)
return ret;
}
ret = wc_ShaUpdate(&ssh->handshake->hash, payload, payloadSz);
if (ret != 0)
return ret;
ret = BundlePacket(ssh);
if (ret == WS_SUCCESS)
ret = SendBuffered(ssh);
return ret;
}
/* This function is clunky, but outdated. */
int SendKexDhReply(WOLFSSH* ssh)
{
DhKey dhKey;
uint8_t f[256];
uint32_t fSz = sizeof(f);
uint8_t fPad = 0;
uint8_t y[256];
uint32_t ySz = sizeof(y);
uint8_t kPad = 0;
RsaKey rsaKey;
uint8_t rsaE[257];
uint32_t rsaESz = sizeof(rsaE);
uint8_t rsaEPad = 0;
uint8_t rsaN[257];
uint32_t rsaNSz = sizeof(rsaN);
uint8_t rsaNPad = 0;
uint32_t rsaKeyBlockSz;
uint8_t sig[512];
uint32_t sigSz = sizeof(sig);
uint32_t sigBlockSz;
uint32_t payloadSz;
uint8_t scratchLen[LENGTH_SZ];
uint32_t scratch = 0;
uint8_t* output;
uint32_t idx;
int ret;
wc_InitDhKey(&dhKey);
switch (ssh->handshake->kexId) {
case ID_DH_GROUP1_SHA1:
wc_DhSetKey(&dhKey, dhPrimeGroup1, dhPrimeGroup1Sz,
dhGenerator, dhGeneratorSz);
break;
case ID_DH_GROUP14_SHA1:
wc_DhSetKey(&dhKey, dhPrimeGroup14, dhPrimeGroup14Sz,
dhGenerator, dhGeneratorSz);
break;
default:
return -1;
}
/* Hash in the server's RSA key. */
wc_InitRsaKey(&rsaKey, ssh->ctx->heap);
ret = wc_RsaPrivateKeyDecode(ssh->ctx->privateKey, &scratch,
&rsaKey, (int)ssh->ctx->privateKeySz);
if (ret < 0)
return ret;
wc_RsaFlattenPublicKey(&rsaKey, rsaE, &rsaESz, rsaN, &rsaNSz);
if (rsaE[0] & 0x80) rsaEPad = 1;
if (rsaN[0] & 0x80) rsaNPad = 1;
rsaKeyBlockSz = (LENGTH_SZ * 3) + 7 + rsaESz + rsaEPad + rsaNSz + rsaNPad;
/* The 7 is for the name "ssh-rsa". */
c32toa(rsaKeyBlockSz, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
c32toa(7, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
wc_ShaUpdate(&ssh->handshake->hash, (const uint8_t*)"ssh-rsa", 7);
c32toa(rsaESz + rsaEPad, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
if (rsaEPad) {
scratchLen[0] = 0;
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, 1);
}
wc_ShaUpdate(&ssh->handshake->hash, rsaE, rsaESz);
c32toa(rsaNSz + rsaNPad, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
if (rsaNPad) {
scratchLen[0] = 0;
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, 1);
}
wc_ShaUpdate(&ssh->handshake->hash, rsaN, rsaNSz);
/* Hash in the client's DH e-value. */
c32toa(ssh->handshake->eSz, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
wc_ShaUpdate(&ssh->handshake->hash, ssh->handshake->e, ssh->handshake->eSz);
/* Make the server's DH f-value, and the shared secret k. */
wc_DhGenerateKeyPair(&dhKey, ssh->rng, y, &ySz, f, &fSz);
if (f[0] & 0x80) fPad = 1;
wc_DhAgree(&dhKey, ssh->k, &ssh->kSz, y, ySz,
ssh->handshake->e, ssh->handshake->eSz);
if (ssh->k[0] & 0x80) kPad = 1;
wc_FreeDhKey(&dhKey);
/* Hash in the server's DH f-value. */
c32toa(fSz + fPad, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
if (fPad) {
scratchLen[0] = 0;
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, 1);
}
wc_ShaUpdate(&ssh->handshake->hash, f, fSz);
/* Hash in the shared secret k. */
c32toa(ssh->kSz + kPad, scratchLen);
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, LENGTH_SZ);
if (kPad) {
scratchLen[0] = 0;
wc_ShaUpdate(&ssh->handshake->hash, scratchLen, 1);
}
wc_ShaUpdate(&ssh->handshake->hash, ssh->k, ssh->kSz);
/* Save the handshake hash value h, and session ID. */
wc_ShaFinal(&ssh->handshake->hash, ssh->h);
ssh->hSz = SHA_DIGEST_SIZE;
if (ssh->sessionIdSz == 0) {
WMEMCPY(ssh->sessionId, ssh->h, ssh->hSz);
ssh->sessionIdSz = ssh->hSz;
}
/* Sign h with the server's RSA private key. */
{
Sha sha;
uint8_t digest[SHA_DIGEST_SIZE];
uint8_t encSig[512];
uint32_t encSigSz;
wc_InitSha(&sha);
wc_ShaUpdate(&sha, ssh->h, ssh->hSz);
wc_ShaFinal(&sha, digest);
encSigSz = wc_EncodeSignature(encSig, digest, sizeof(digest), SHAh);
if (encSigSz <= 0) {
WLOG(WS_LOG_DEBUG, "SendKexDhReply: Bad Encode Sig");
}
else {
/* At this point, sigSz should already be sizeof(sig) */
sigSz = wc_RsaSSL_Sign(encSig, encSigSz,
sig, sigSz, &rsaKey, ssh->rng);
if (sigSz <= 0) {
WLOG(WS_LOG_DEBUG, "SendKexDhReply: Bad RSA Sign");
}
else {
/* Success */
}
}
}
wc_FreeRsaKey(&rsaKey);
sigBlockSz = (LENGTH_SZ * 2) + 7 + sigSz;
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. */
payloadSz = MSG_ID_SZ + (LENGTH_SZ * 3) +
rsaKeyBlockSz + fSz + fPad + sigBlockSz;
ret = PreparePacket(ssh, payloadSz);
if (ret != WS_SUCCESS)
return ret;
output = ssh->outputBuffer.buffer;
idx = ssh->outputBuffer.length;
output[idx++] = MSGID_KEXDH_REPLY;
/* Copy the rsaKeyBlock into the buffer. */
c32toa(rsaKeyBlockSz, output + idx);
idx += LENGTH_SZ;
c32toa(7, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, "ssh-rsa", 7);
idx += 7;
c32toa(rsaESz + rsaEPad, output + idx);
idx += LENGTH_SZ;
if (rsaEPad) output[idx++] = 0;
WMEMCPY(output + idx, rsaE, rsaESz);
idx += rsaESz;
c32toa(rsaNSz + rsaNPad, output + idx);
idx += LENGTH_SZ;
if (rsaNPad) output[idx++] = 0;
WMEMCPY(output + idx, rsaN, rsaNSz);
idx += rsaNSz;
c32toa(fSz + fPad, output + idx);
idx += LENGTH_SZ;
if (fPad) output[idx++] = 0;
WMEMCPY(output + idx, f, fSz);
idx += fSz;
c32toa(sigBlockSz, output + idx);
idx += LENGTH_SZ;
c32toa(7, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, "ssh-rsa", 7);
idx += 7;
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);
return ret;
}
int SendNewKeys(WOLFSSH* ssh)
{
uint8_t* output;
uint32_t idx = 0;
int ret;
ret = PreparePacket(ssh, MSG_ID_SZ);
if (ret != WS_SUCCESS)
return ret;
output = ssh->outputBuffer.buffer;
idx = ssh->outputBuffer.length;
output[idx++] = MSGID_NEWKEYS;
ssh->outputBuffer.length = idx;
ret = BundlePacket(ssh);
if (ret == WS_SUCCESS)
ret = SendBuffered(ssh);
if (ret != WS_SUCCESS)
return ret;
ssh->blockSz = ssh->handshake->blockSz;
ssh->encryptId = ssh->handshake->encryptId;
ssh->macSz = ssh->handshake->macSz;
ssh->macId = ssh->handshake->macId;
switch (ssh->encryptId) {
case ID_NONE:
WLOG(WS_LOG_DEBUG, "SNK: using cipher none");
break;
case ID_AES128_CBC:
WLOG(WS_LOG_DEBUG, "SNK: using cipher aes128-cbc");
ret = wc_AesSetKey(&ssh->encryptCipher.aes,
ssh->encKeyServer, ssh->encKeyServerSz,
ssh->ivServer, AES_ENCRYPTION);
break;
default:
WLOG(WS_LOG_DEBUG, "SNK: using cipher invalid");
break;
}
ssh->txCount = 0;
return ret;
}
int SendUnimplemented(WOLFSSH* ssh)
{
uint8_t* output;
uint32_t idx = 0;
int ret;
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ);
if (ret != WS_SUCCESS)
return ret;
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 = SendBuffered(ssh);
return ret;
}
int SendDisconnect(WOLFSSH* ssh, uint32_t reason)
{
uint8_t* output;
uint32_t idx = 0;
int ret;
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + (LENGTH_SZ * 2));
if (ret != WS_SUCCESS)
return ret;
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 = SendBuffered(ssh);
return ret;
}
int SendIgnore(WOLFSSH* ssh, const unsigned char* data, uint32_t dataSz)
{
uint8_t* output;
uint32_t idx = 0;
int ret;
if (ssh == NULL || (data == NULL && dataSz > 0))
return WS_BAD_ARGUMENT;
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + dataSz);
if (ret != WS_SUCCESS)
return ret;
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 = SendBuffered(ssh);
return ret;
}
static const char cannedLangTag[] = "en-us";
static const uint32_t cannedLangTagSz = sizeof(cannedLangTag) - 1;
int SendDebug(WOLFSSH* ssh, byte alwaysDisplay, const char* msg)
{
uint32_t msgSz;
uint8_t* output;
uint32_t idx = 0;
int ret;
if (ssh == NULL)
return WS_BAD_ARGUMENT;
msgSz = (msg != NULL) ? (uint32_t)WSTRLEN(msg) : 0;
ret = PreparePacket(ssh,
MSG_ID_SZ + BOOLEAN_SZ + (LENGTH_SZ * 2) +
msgSz + cannedLangTagSz);
if (ret != WS_SUCCESS)
return ret;
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 = SendBuffered(ssh);
return ret;
}
const char userAuthName[] = "ssh-userauth";
int SendServiceAccept(WOLFSSH* ssh)
{
uint32_t nameSz;
uint8_t* output;
uint32_t idx;
int ret;
if (ssh == NULL)
return WS_BAD_ARGUMENT;
nameSz = (uint32_t)WSTRLEN(userAuthName);
ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + nameSz);
if (ret != WS_SUCCESS)
return ret;
output = ssh->outputBuffer.buffer;
idx = ssh->outputBuffer.length;
output[idx++] = MSGID_SERVICE_ACCEPT;
c32toa(nameSz, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, userAuthName, nameSz);
idx += nameSz;
ssh->outputBuffer.length = idx;
ret = BundlePacket(ssh);
if (ret == WS_SUCCESS)
ret = SendUserAuthBanner(ssh);
return ret;
}
static const char cannedAuths[] = "publickey,password";
static const uint32_t cannedAuthsSz = sizeof(cannedAuths) - 1;
int SendUserAuthFailure(WOLFSSH* ssh, uint8_t partialSuccess)
{
uint8_t* output;
uint32_t idx;
int ret;
(void)partialSuccess;
WLOG(WS_LOG_DEBUG, "Entering SendUserAuthFailure()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
ret = PreparePacket(ssh,
MSG_ID_SZ + LENGTH_SZ + cannedAuthsSz + BOOLEAN_SZ);
if (ret != WS_SUCCESS)
return ret;
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++] = 0;
ssh->outputBuffer.length = idx;
ret = BundlePacket(ssh);
if (ret == WS_SUCCESS)
ret = SendBuffered(ssh);
return ret;
}
int SendUserAuthSuccess(WOLFSSH* ssh)
{
uint8_t* output;
uint32_t idx;
int ret;
if (ssh == NULL)
return WS_BAD_ARGUMENT;
ret = PreparePacket(ssh, MSG_ID_SZ);
if (ret != WS_SUCCESS)
return ret;
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 = SendBuffered(ssh);
return ret;
}
int SendUserAuthPkOk(WOLFSSH* ssh,
const uint8_t* algoName, uint32_t algoNameSz,
const uint8_t* publicKey, uint32_t publicKeySz)
{
uint8_t* output;
uint32_t idx;
int ret;
if (ssh == NULL ||
algoName == NULL || algoNameSz == 0 ||
publicKey == NULL || publicKeySz == 0) {
return WS_BAD_ARGUMENT;
}
PreparePacket(ssh, MSG_ID_SZ + (LENGTH_SZ * 2) + algoNameSz + publicKeySz);
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;
BundlePacket(ssh);
ret = SendBuffered(ssh);
return ret;
}
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 uint32_t cannedBannerSz = sizeof(cannedBanner) - 1;
int SendUserAuthBanner(WOLFSSH* ssh)
{
uint8_t* output;
uint32_t idx;
int ret;
if (ssh == NULL)
return WS_BAD_ARGUMENT;
ret = PreparePacket(ssh, MSG_ID_SZ + (LENGTH_SZ * 2) +
cannedBannerSz + cannedLangTagSz);
if (ret != WS_SUCCESS)
return ret;
output = ssh->outputBuffer.buffer;
idx = ssh->outputBuffer.length;
output[idx++] = MSGID_USERAUTH_BANNER;
c32toa(cannedBannerSz, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, cannedBanner, cannedBannerSz);
idx += cannedBannerSz;
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 = SendBuffered(ssh);
return ret;
}
int SendChannelOpenConf(WOLFSSH* ssh)
{
uint8_t* output;
uint32_t idx;
int ret;
Channel* channel;
WLOG(WS_LOG_DEBUG, "Entering SendChannelOpenConf()");
if (ssh == NULL) {
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenConf(), ret = %d",
WS_BAD_ARGUMENT);
return WS_BAD_ARGUMENT;
}
channel = &ssh->channel;
ret = PreparePacket(ssh, MSG_ID_SZ + (UINT32_SZ * 4));
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenConf(), ret = %d", ret);
return ret;
}
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 = SendBuffered(ssh);
WLOG(WS_LOG_DEBUG, "Leaving SendChannelOpenConf(), ret = %d", ret);
return ret;
}
int SendChannelData(WOLFSSH* ssh, uint32_t peerChannel,
uint8_t* data, uint32_t dataSz)
{
uint8_t* output;
uint32_t idx;
int ret;
Channel* channel;
WLOG(WS_LOG_DEBUG, "Entering SendChannelData()");
if (ssh == NULL)
return WS_BAD_ARGUMENT;
channel = &ssh->channel;
if (channel->peerChannel != peerChannel) {
WLOG(WS_LOG_DEBUG, "Invalid peer channel");
}
if (channel->peerWindowSz < dataSz) {
WLOG(WS_LOG_DEBUG, "Peer window too small");
}
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
if (ret != WS_SUCCESS)
return ret;
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)
ret = SendBuffered(ssh);
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);
WLOG(WS_LOG_DEBUG, "Leaving SendChannelData()");
return ret;
}
#define LINE_WIDTH 16
void DumpOctetString(const uint8_t* input, uint32_t 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");
}
}