Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,91 @@ int wolfSSH_AGENT_enable(WOLFSSH* ssh, byte isEnabled)
}


int wolfSSH_AGENT_ChannelOpen(WOLFSSH* ssh)
{
WOLFSSH_AGENT_CTX* newAgent = NULL;
WOLFSSH_CHANNEL* newChannel = NULL;
int ret = WS_SUCCESS;
/* wolfSSH_accept() clears only want-read/want-write/auth-pending, so a
* WS_BAD_ARGUMENT latched by a poll kills the handshake. */
int recordError = 0;

WLOG_ENTER();

if (ssh == NULL)
ret = WS_SSH_NULL_E;
else if (ssh->ctx->side != WOLFSSH_ENDPOINT_SERVER) {
/* Server side only. wolfSSH_connect() sets ssh->agent too, so the
* checks below would report a channel a client never opened. */
ret = WS_BAD_ARGUMENT;
}
else if (SendAfterDisconnect(ssh)) {
/* The session is over, so neither a new open nor the flush of one
* queued before the disconnect may go out. RFC 4253 section 11.1.
* WS_DISCONNECT is in ssh->error, where the rest of the API puts
* it. */
ret = WS_FATAL_ERROR;
}
else if (!ssh->useAgent) {
Comment thread
ejohnstown marked this conversation as resolved.
/* Nothing asked for agent forwarding on this session. */
ret = WS_BAD_ARGUMENT;
}
else if (ssh->agent == NULL) {
Comment thread
ejohnstown marked this conversation as resolved.
/* Nothing else sets ssh->agent, so a NULL one means "not opened
* yet". Idempotent, so a poll cannot open a second channel. */
WLOG(WS_LOG_AGENT, "Starting agent channel");

newAgent = wolfSSH_AGENT_new(ssh->ctx->heap);
if (newAgent == NULL)
ret = WS_MEMORY_E;

if (ret == WS_SUCCESS) {
newChannel = ChannelNew(ssh, ID_CHANTYPE_AUTH_AGENT,
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
if (newChannel == NULL)
ret = WS_MEMORY_E;
}

if (ret == WS_SUCCESS) {
recordError = 1;
ret = SendChannelOpenSession(ssh, newChannel);

if (ret < WS_SUCCESS
&& ret != WS_WANT_WRITE && ret != WS_WANT_READ) {
ChannelDelete(newChannel, ssh->ctx->heap);
}
else {
/* Publish on a queued open too, so a retry takes the
* already-open path rather than opening a second. */
ChannelAppend(ssh, newChannel);
newAgent->channel = newChannel->channel;
ssh->agent = newAgent;
newAgent = NULL;
if (ssh->ctx->agentCb) {
ssh->ctx->agentCb(WOLFSSH_AGENT_LOCAL_SETUP,
ssh->agentCbCtx);
}
}
}

if (newAgent != NULL)
wolfSSH_AGENT_free(newAgent);
}
else if (wolfSSH_OutputPending(ssh)) {
/* Any queued output, not just this open. Flush it rather than
* report a success the peer hasn't seen. */
recordError = 1;
ret = wolfSSH_SendPacket(ssh);
Comment thread
ejohnstown marked this conversation as resolved.
}

if (recordError)
ssh->error = ret;
Comment thread
ejohnstown marked this conversation as resolved.

WLOG_LEAVE(ret);
return ret;
}


int wolfSSH_AGENT_worker(WOLFSSH* ssh)
{
int ret = WS_SUCCESS;
Expand Down
58 changes: 8 additions & 50 deletions src/ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -567,10 +567,6 @@ static int DoReceiveHandshake(WOLFSSH* ssh)
#endif /* !NO_WOLFSSH_SERVER || !NO_WOLFSSH_CLIENT */


/* Defined below, ahead of both drivers; either can be the only one built. */
static int SendAfterDisconnect(WOLFSSH* ssh);


#ifndef NO_WOLFSSH_SERVER

const char acceptError[] = "accept error: %s, %d";
Expand Down Expand Up @@ -764,52 +760,17 @@ int wolfSSH_accept(WOLFSSH* ssh)
#endif /* WOLFSSH_SFTP and !NO_WOLFSSH_SERVER */
#ifdef WOLFSSH_AGENT
if (ssh->useAgent) {
WOLFSSH_AGENT_CTX* newAgent;
WOLFSSH_CHANNEL* newChannel;

WLOG(WS_LOG_AGENT, "Starting agent channel");

newAgent = wolfSSH_AGENT_new(ssh->ctx->heap);
if (newAgent == NULL) {
ssh->error = WS_MEMORY_E;
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_ERROR;
}
int agentRet = wolfSSH_AGENT_ChannelOpen(ssh);

newChannel = ChannelNew(ssh, ID_CHANTYPE_AUTH_AGENT,
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
if (newChannel == NULL) {
wolfSSH_AGENT_free(newAgent);
ssh->error = WS_MEMORY_E;
if (agentRet < WS_SUCCESS) {
/* WS_FATAL_ERROR is the disconnect, which already
* recorded WS_DISCONNECT; keep that. */
if (agentRet != WS_FATAL_ERROR)
ssh->error = agentRet;
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_FATAL_ERROR;
}

ssh->error = SendChannelOpenSession(ssh, newChannel);
if (ssh->error < WS_SUCCESS) {
if (ssh->error == WS_WANT_WRITE ||
ssh->error == WS_WANT_READ) {
ChannelAppend(ssh, newChannel);
}
else {
ChannelDelete(newChannel, ssh->ctx->heap);
wolfSSH_AGENT_free(newAgent);
}
WLOG(WS_LOG_DEBUG, acceptError,
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
return WS_FATAL_ERROR;
}
ChannelAppend(ssh, newChannel);
newAgent->channel = newChannel->channel;
if (ssh->ctx->agentCb) {
ssh->ctx->agentCb(WOLFSSH_AGENT_LOCAL_SETUP,
ssh->agentCbCtx);
}
if (ssh->agent != NULL)
wolfSSH_AGENT_free(ssh->agent);
ssh->agent = newAgent;
}
#endif /* WOLFSSH_AGENT */
ssh->acceptState = ACCEPT_CLIENT_SESSION_ESTABLISHED;
Expand Down Expand Up @@ -1134,11 +1095,8 @@ int wolfSSH_connect(WOLFSSH* ssh)
#endif /* NO_WOLFSSH_CLIENT */


/* A disconnect, sent or received, ends the session, so nothing further may
* go out. RFC 4253 section 11.1. Reads are deliberately not gated on this:
* channel data that arrived before the disconnect is still the caller's.
* Call only after ssh has been checked for NULL. */
static int SendAfterDisconnect(WOLFSSH* ssh)
/* See wolfssh/internal.h for the contract. */
int SendAfterDisconnect(WOLFSSH* ssh)
{
if (ssh->disconnected) {
WLOG(WS_LOG_DEBUG, "Send attempted after a disconnect");
Expand Down
146 changes: 146 additions & 0 deletions tests/regress.c
Original file line number Diff line number Diff line change
Expand Up @@ -4437,6 +4437,144 @@ static void TestAgentChannelNullAgentSendsOpenFail(void)

FreeChannelOpenHarness(&harness);
}

/* Nothing asked for forwarding, so the open is refused rather than started.
* The refusal is the documented answer to a poll, so it must not land in
* ssh->error: wolfSSH_accept() would then abort with WS_INVALID_STATE_E. */
static void TestAgentChannelOpenWithoutRequest(void)
{
ChannelOpenHarness harness;

InitChannelOpenHarness(&harness, NULL, 0);

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_BAD_ARGUMENT);
AssertNull(harness.ssh->agent);
AssertIntEQ(harness.io.outSz, 0);
AssertIntEQ(harness.ssh->error, WS_SUCCESS);

/* The handshake survives the poll: no input, so accept only wants read. */
AssertIntEQ(wolfSSH_accept(harness.ssh), WS_FATAL_ERROR);
AssertIntEQ(harness.ssh->error, WS_WANT_READ);

FreeChannelOpenHarness(&harness);
}

/* A poll after the peer disconnects must not open a channel or put anything
* on the wire. RFC 4253 section 11.1: the session is over. */
static void TestAgentChannelOpenAfterDisconnect(void)
{
ChannelOpenHarness harness;

InitChannelOpenHarness(&harness, NULL, 0);
harness.ssh->useAgent = 1;
harness.ssh->disconnected = 1;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_FATAL_ERROR);
AssertNull(harness.ssh->agent);
AssertIntEQ(harness.ssh->channelListSz, 0);
AssertIntEQ(harness.io.outSz, 0);
AssertIntEQ(harness.ssh->error, WS_DISCONNECT);

FreeChannelOpenHarness(&harness);
}

/* An open queued before the disconnect is not flushed either: those bytes
* belong to a session that is over, the same rule wolfSSH_shutdown() applies
* to everything but its own queued disconnect. */
static void TestAgentChannelOpenQueuedThenDisconnect(void)
{
ChannelOpenHarness harness;

InitChannelOpenHarness(&harness, NULL, 0);
harness.ssh->useAgent = 1;
harness.io.blockNext = 1;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_WANT_WRITE);
AssertIntEQ(harness.io.outSz, 0);

harness.ssh->disconnected = 1;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_FATAL_ERROR);
AssertIntEQ(harness.io.outSz, 0);
AssertIntEQ(harness.ssh->error, WS_DISCONNECT);

FreeChannelOpenHarness(&harness);
}

/* A queued open publishes the agent, so the caller's next poll must finish
* the send rather than report a success the peer never saw, and must not
* open a second channel. */
static void TestAgentChannelOpenFlushesQueuedOpen(void)
{
ChannelOpenHarness harness;
word32 outSz;

InitChannelOpenHarness(&harness, NULL, 0);
harness.ssh->useAgent = 1;
harness.io.blockNext = 1;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_WANT_WRITE);
AssertNotNull(harness.ssh->agent);
AssertIntEQ(harness.ssh->channelListSz, 1);
AssertIntEQ(harness.io.outSz, 0);

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_SUCCESS);
AssertIntEQ(harness.ssh->channelListSz, 1);
AssertTrue(harness.io.outSz > 0);
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
MSGID_CHANNEL_OPEN);

/* The flushed open is the answer wolfSSH_accept() retries on: success,
* no second channel, no new packet, ssh->error untouched. */
outSz = harness.io.outSz;
harness.ssh->error = WS_SUCCESS;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_SUCCESS);
AssertIntEQ(harness.ssh->channelListSz, 1);
AssertIntEQ(harness.io.outSz, outSz);
AssertIntEQ(harness.ssh->error, WS_SUCCESS);

FreeChannelOpenHarness(&harness);
}

/* A send that fails outright, rather than blocking, leaves nothing behind,
* so a later poll starts the open over. */
static void TestAgentChannelOpenSendFailureCleansUp(void)
{
ChannelOpenHarness harness;

InitChannelOpenHarness(&harness, NULL, 0);
harness.ssh->useAgent = 1;
/* No room, so MemSend reports a general error. */
harness.io.outCap = 0;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_SOCKET_ERROR_E);
AssertNull(harness.ssh->agent);
AssertIntEQ(harness.ssh->channelListSz, 0);
AssertIntEQ(harness.io.outSz, 0);
AssertIntEQ(harness.ssh->error, WS_SOCKET_ERROR_E);

FreeChannelOpenHarness(&harness);
}

#ifndef NO_WOLFSSH_CLIENT
/* Server-side call. A client has an ssh->agent of its own, so answering the
* poll from it would report a channel that was never opened. */
static void TestAgentChannelOpenOnClientRefused(void)
{
ChannelOpenHarness harness;

InitChannelOpenHarnessClient(&harness, NULL, 0);
harness.ssh->useAgent = 1;

AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_BAD_ARGUMENT);
AssertIntEQ(harness.ssh->channelListSz, 0);
AssertIntEQ(harness.io.outSz, 0);
AssertIntEQ(harness.ssh->error, WS_SUCCESS);

FreeChannelOpenHarness(&harness);
}
#endif /* !NO_WOLFSSH_CLIENT */
#endif


Expand Down Expand Up @@ -13427,6 +13565,14 @@ int main(int argc, char** argv)
#endif
#ifdef WOLFSSH_AGENT
TestAgentChannelNullAgentSendsOpenFail();
TestAgentChannelOpenWithoutRequest();
TestAgentChannelOpenFlushesQueuedOpen();
TestAgentChannelOpenAfterDisconnect();
TestAgentChannelOpenQueuedThenDisconnect();
TestAgentChannelOpenSendFailureCleansUp();
#ifndef NO_WOLFSSH_CLIENT
TestAgentChannelOpenOnClientRefused();
#endif
#endif
#endif /* NO_WOLFSSH_SERVER */
#if defined(WOLFSSH_AGENT) && !defined(WOLFSSH_NO_ED25519) \
Expand Down
13 changes: 13 additions & 0 deletions wolfssh/agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ WOLFSSH_API int wolfSSH_CTX_set_agent_cb(WOLFSSH_CTX* ctx,
WOLFSSH_API int wolfSSH_set_agent_cb_ctx(WOLFSSH* ssh, void* ctx);
WOLFSSH_API int wolfSSH_CTX_AGENT_enable(WOLFSSH_CTX* ctx, byte isEnabled);
WOLFSSH_API int wolfSSH_AGENT_enable(WOLFSSH* ssh, byte isEnabled);
/* Server side. Opens the auth-agent@openssh.com channel to the client once
* the peer's auth-agent-req@openssh.com asks for forwarding. wolfSSH_accept()
* does it on the default path; an application driving its own channels polls
* this instead. Opens one channel, then flushes what of the open is queued.
* Returns WS_SUCCESS, WS_BAD_ARGUMENT before the peer asks or on a client
* session, WS_WANT_READ or WS_WANT_WRITE while output is still queued,
* WS_FATAL_ERROR with WS_DISCONNECT in ssh->error once the session is over,
* WS_SSH_NULL_E, WS_MEMORY_E, or whatever the send reports. WS_SUCCESS says
* the open went out, not that the peer took it; a refusal reaches the
* channel-open-fail callback.
* Only that and the send record in ssh->error, so a poll ahead of the peer's
* request leaves the session fit for wolfSSH_accept(). */
WOLFSSH_API int wolfSSH_AGENT_ChannelOpen(WOLFSSH* ssh);
WOLFSSH_LOCAL int wolfSSH_AGENT_worker(WOLFSSH* ssh);
WOLFSSH_API int wolfSSH_AGENT_Relay(WOLFSSH* ssh,
const byte* msg, word32* msgSz, byte* rsp, word32* rspSz);
Expand Down
6 changes: 6 additions & 0 deletions wolfssh/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,12 @@ enum ChannelOpenFailReasons {
OPEN_RESOURCE_SHORTAGE
};

/* A disconnect, sent or received, ends the session, so nothing further may
* go out. RFC 4253 section 11.1. Returns 1 and records WS_DISCONNECT in
* ssh->error when the session is over, 0 otherwise. Reads are deliberately
* not gated on this: channel data that arrived before the disconnect is
* still the caller's. Call only after ssh has been checked for NULL. */
WOLFSSH_LOCAL int SendAfterDisconnect(WOLFSSH* ssh);
WOLFSSH_LOCAL int DoReceive(WOLFSSH* ssh);
WOLFSSH_LOCAL int DoProtoId(WOLFSSH* ssh);
WOLFSSH_LOCAL int wolfSSH_SendPacket(WOLFSSH* ssh);
Expand Down
Loading