mirror of https://github.com/wolfSSL/wolfssh.git
agent: let the application open the agent channel
The one server-side site that opens auth-agent@openssh.com sits inside wolfSSH_accept(), so an application driving its own channels cannot reach it: the session records the request and no channel follows. - add wolfSSH_AGENT_ChannelOpen(), the same open lifted out of accept(), which still calls it - it reports WS_BAD_ARGUMENT until the peer asks and on a client session, and is idempotent after, so an application can poll it - publish the agent on a queued open too, so a retry after WS_WANT_WRITE finds it rather than opening a second channel and leaking the first - flush what is left of a queued open on the next call, rather than reporting a success the peer never saw - record ssh->error from the send alone, so neither a poll ahead of the request nor a failed allocation stops accept() continuingpull/1245/head
parent
85f7978eb4
commit
41f171d982
78
src/agent.c
78
src/agent.c
|
|
@ -1731,6 +1731,84 @@ 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 (!ssh->useAgent) {
|
||||
/* Nothing asked for agent forwarding on this session. */
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
}
|
||||
else if (ssh->agent == NULL) {
|
||||
/* 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);
|
||||
}
|
||||
|
||||
if (recordError)
|
||||
ssh->error = ret;
|
||||
|
||||
WLOG_LEAVE(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int wolfSSH_AGENT_worker(WOLFSSH* ssh)
|
||||
{
|
||||
int ret = WS_SUCCESS;
|
||||
|
|
|
|||
42
src/ssh.c
42
src/ssh.c
|
|
@ -813,52 +813,12 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
WLOG(WS_LOG_DEBUG, acceptError,
|
||||
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
|
||||
return WS_FATAL_ERROR;
|
||||
}
|
||||
|
||||
ssh->error = SendChannelOpenSession(ssh, newChannel);
|
||||
ssh->error = wolfSSH_AGENT_ChannelOpen(ssh);
|
||||
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;
|
||||
|
|
|
|||
102
tests/regress.c
102
tests/regress.c
|
|
@ -4437,6 +4437,102 @@ 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 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
|
||||
|
||||
|
||||
|
|
@ -13427,6 +13523,12 @@ int main(int argc, char** argv)
|
|||
#endif
|
||||
#ifdef WOLFSSH_AGENT
|
||||
TestAgentChannelNullAgentSendsOpenFail();
|
||||
TestAgentChannelOpenWithoutRequest();
|
||||
TestAgentChannelOpenFlushesQueuedOpen();
|
||||
TestAgentChannelOpenSendFailureCleansUp();
|
||||
#ifndef NO_WOLFSSH_CLIENT
|
||||
TestAgentChannelOpenOnClientRefused();
|
||||
#endif
|
||||
#endif
|
||||
#endif /* NO_WOLFSSH_SERVER */
|
||||
#if defined(WOLFSSH_AGENT) && !defined(WOLFSSH_NO_ED25519) \
|
||||
|
|
|
|||
|
|
@ -181,6 +181,18 @@ 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_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 the send records 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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue