diff --git a/ssh1login.c b/ssh1login.c index 803e590e..64782868 100644 --- a/ssh1login.c +++ b/ssh1login.c @@ -12,6 +12,12 @@ #include "sshppl.h" #include "sshcr.h" +typedef struct agent_key { + RSAKey key; + strbuf *comment; + ptrlen blob; /* only used during initial parsing of agent response */ +} agent_key; + struct ssh1_login_state { int crState; @@ -47,11 +53,11 @@ struct ssh1_login_state { void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* response from SSH agent */ - int keyi, nkeys; + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; bool authed; RSAKey key; - mp_int *challenge; - strbuf *agent_comment; int dlgret; Filename *keyfile; RSAKey servkey, hostkey; @@ -99,7 +105,6 @@ PacketProtocolLayer *ssh1_login_new( s->savedhost = dupstr(host); s->savedport = port; s->successor_layer = successor_layer; - s->agent_comment = strbuf_new(); return &s->ppl; } @@ -118,9 +123,15 @@ static void ssh1_login_free(PacketProtocolLayer *ppl) if (s->publickey_blob) strbuf_free(s->publickey_blob); sfree(s->publickey_comment); - strbuf_free(s->agent_comment); if (s->cur_prompt) free_prompts(s->cur_prompt); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + freersakey(&s->agent_keys[i].key); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } sfree(s->agent_response_to_free); if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); @@ -504,122 +515,165 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) get_uint32(s->asrc); /* skip length field */ if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - s->nkeys = toint(get_uint32(s->asrc)); - if (s->nkeys < 0) { - ppl_logevent("Pageant reported negative key count %d", - s->nkeys); - s->nkeys = 0; - } - ppl_logevent("Pageant has %d SSH-1 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - size_t start, end; - start = s->asrc->pos; - get_rsa_ssh1_pub(s->asrc, &s->key, - RSA_SSH1_EXPONENT_FIRST); - end = s->asrc->pos; - strbuf_clear(s->agent_comment); - put_datapl(s->agent_comment, get_string(s->asrc)); - if (get_err(s->asrc)) { - ppl_logevent("Pageant key list packet was truncated"); - break; - } - if (s->publickey_blob) { - ptrlen keystr = make_ptrlen( - (const char *)s->asrc->data + start, end - start); + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; - if (keystr.len == s->publickey_blob->len && - !memcmp(keystr.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", s->keyi); - s->tried_publickey = true; - } else - /* Skip non-configured key */ - continue; + /* + * Check that the agent response is well formed. + */ + for (size_t i = 0; i < nkeys; i++) { + get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto parsed_agent_query; } - ppl_logevent("Trying Pageant key #%d", s->keyi); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ppl_logevent("Key refused"); - continue; + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + memset(&s->agent_keys[i].key, 0, + sizeof(s->agent_keys[i].key)); + + const char *blobstart = get_ptr(s->asrc); + get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, + RSA_SSH1_EXPONENT_FIRST); + const char *blobend = get_ptr(s->asrc); + + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + s->agent_keys[i].blob = make_ptrlen( + blobstart, blobend - blobstart); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent + * down to only that one, or none if it's not + * there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) + break; } - ppl_logevent("Received RSA challenge"); - s->challenge = get_mp_ssh1(pktin); + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { + ppl_logevent("Configured key file not in Pageant"); + s->agent_key_index = 0; + s->agent_key_limit = 0; + } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; + } + } else { + ppl_logevent("Failed to get reply from Pageant"); + } + parsed_agent_query:; + + for (; s->agent_key_index < s->agent_key_limit; + s->agent_key_index++) { + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, + s->agent_keys[s->agent_key_index].key.modulus); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ppl_logevent("Key refused"); + continue; + } + ppl_logevent("Received RSA challenge"); + + { + mp_int *challenge = get_mp_ssh1(pktin); if (get_err(pktin)) { - mp_free(s->challenge); + mp_free(challenge); ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " "was badly formatted"); return; } - { - strbuf *agentreq; - const char *ret; + strbuf *agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - put_uint32(agentreq, mp_get_nbits(s->key.modulus)); - put_mp_ssh1(agentreq, s->key.exponent); - put_mp_ssh1(agentreq, s->key.modulus); - put_mp_ssh1(agentreq, s->challenge); - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh1_login_agent_query(s, agentreq); - strbuf_free(agentreq); - crMaybeWaitUntilV(!s->auth_agent_query); + rsa_ssh1_public_blob( + BinarySink_UPCAST(agentreq), + &s->agent_keys[s->agent_key_index].key, + RSA_SSH1_EXPONENT_FIRST); - ret = s->agent_response.ptr; - if (ret) { - if (s->agent_response.len >= 5+16 && - ret[4] == SSH1_AGENT_RSA_RESPONSE) { - ppl_logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV( - (pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - ppl_logevent("Pageant's response " - "accepted"); - if (flags & FLAG_VERBOSE) { - ptrlen comment = ptrlen_from_strbuf( - s->agent_comment); - ppl_printf("Authenticated using RSA " - "key \"%.*s\" from " - "agent\r\n", - PTRLEN_PRINTF(comment)); - } - s->authed = true; - } else - ppl_logevent("Pageant's response not " - "accepted"); - } else { - ppl_logevent("Pageant failed to answer " - "challenge"); - sfree((char *)ret); - } - } else { - ppl_logevent("No reply received from Pageant"); - } - } - mp_free(s->key.exponent); - mp_free(s->key.modulus); - mp_free(s->challenge); - if (s->authed) - break; + put_mp_ssh1(agentreq, challenge); + mp_free(challenge); + + put_data(agentreq, s->session_id, 16); + put_uint32(agentreq, 1); /* response format */ + ssh1_login_agent_query(s, agentreq); + strbuf_free(agentreq); + crMaybeWaitUntilV(!s->auth_agent_query); } - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - if (s->publickey_blob && !s->tried_publickey) - ppl_logevent("Configured key file not in Pageant"); - } else { - ppl_logevent("Failed to get reply from Pageant"); + + { + const unsigned char *ret = s->agent_response.ptr; + if (ret) { + if (s->agent_response.len >= 5+16 && + ret[4] == SSH1_AGENT_RSA_RESPONSE) { + ppl_logevent("Sending Pageant's response"); + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, ret + 5, 16); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent("Pageant's response " + "accepted"); + if (flags & FLAG_VERBOSE) { + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index]. + comment); + ppl_printf("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", + PTRLEN_PRINTF(comment)); + } + s->authed = true; + } else + ppl_logevent("Pageant's response not " + "accepted"); + } else { + ppl_logevent("Pageant failed to answer " + "challenge"); + sfree((char *)ret); + } + } else { + ppl_logevent("No reply received from Pageant"); + } + } + if (s->authed) + break; } if (s->authed) break; diff --git a/ssh2userauth.c b/ssh2userauth.c index 4d0d0392..2e7ffb0c 100644 --- a/ssh2userauth.c +++ b/ssh2userauth.c @@ -18,6 +18,11 @@ #define BANNER_LIMIT 131072 +typedef struct agent_key { + strbuf *blob, *comment; + ptrlen algorithm; +} agent_key; + struct ssh2_userauth_state { int crState; @@ -69,9 +74,9 @@ struct ssh2_userauth_state { void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* for reading SSH agent response */ - size_t pkblob_pos_in_agent; - int keyi, nkeys; - ptrlen pk, alg, comment; + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; int len; PktOut *pktout; bool want_user_input; @@ -173,6 +178,13 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) if (s->successor_layer) ssh_ppl_free(s->successor_layer); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + strbuf_free(s->agent_keys[i].blob); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } sfree(s->agent_response_to_free); if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); @@ -300,8 +312,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). */ - s->nkeys = 0; - s->pkblob_pos_in_agent = 0; if (s->tryagent && agent_exists()) { ppl_logevent("Pageant is running. Requesting keys."); @@ -317,48 +327,75 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) get_uint32(s->asrc); /* skip length field */ if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - int keyi; - - s->nkeys = toint(get_uint32(s->asrc)); + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; /* - * Vet the Pageant response to ensure that the key count - * and blob lengths make sense. + * Check that the agent response is well formed. */ - if (s->nkeys < 0) { - ppl_logevent("Pageant response contained a negative" - " key count %d", s->nkeys); - s->nkeys = 0; - goto done_agent_query; - } else { - ppl_logevent("Pageant has %d SSH-2 keys", s->nkeys); + for (size_t i = 0; i < nkeys; i++) { + get_string(s->asrc); /* blob */ + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto done_agent_query; + } + } - /* See if configured key is in agent. */ - for (keyi = 0; keyi < s->nkeys; keyi++) { - size_t pos = s->asrc->pos; - ptrlen blob = get_string(s->asrc); - get_string(s->asrc); /* skip comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + s->agent_keys[i].blob = strbuf_new(); + put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); - if (s->publickey_blob && - blob.len == s->publickey_blob->len && - !memcmp(blob.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", keyi); - s->keyi = keyi; - s->pkblob_pos_in_agent = pos; + /* Also, extract the algorithm string from the start + * of the public-key blob. */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + s->agent_keys[i].blob)); + s->agent_keys[i].algorithm = get_string(src); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent down + * to only that one, or none if it's not there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( + s->agent_keys[i].blob))) break; - } } - if (s->publickey_blob && !s->pkblob_pos_in_agent) { + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { ppl_logevent("Configured key file not in Pageant"); - s->nkeys = 0; + s->agent_key_index = 0; + s->agent_key_limit = 0; } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; } } else { ppl_logevent("Failed to get reply from Pageant"); @@ -457,17 +494,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->tried_pubkey_config = false; s->kbd_inter_refused = false; - - /* Reset agent request state. */ s->done_agent = false; - if (s->agent_response.ptr) { - if (s->pkblob_pos_in_agent) { - s->asrc->pos = s->pkblob_pos_in_agent; - } else { - s->asrc->pos = 9; /* skip length + type + key count */ - s->keyi = 0; - } - } while (1) { /* @@ -688,7 +715,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else #endif /* NO_GSSAPI */ - if (s->can_pubkey && !s->done_agent && s->nkeys) { + if (s->can_pubkey && !s->done_agent && + s->agent_key_index < s->agent_key_limit) { /* * Attempt public-key authentication using a key from Pageant. @@ -696,16 +724,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - ppl_logevent("Trying Pageant key #%d", s->keyi); - - /* Unpack key from agent response */ - s->pk = get_string(s->asrc); - s->comment = get_string(s->asrc); - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, s->pk); - s->alg = get_string(src); - } + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); /* See if server will accept it */ s->pktout = ssh_bpp_new_pktout( @@ -715,8 +734,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; @@ -729,11 +750,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else { strbuf *agentreq, *sigdata; + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].comment); if (flags & FLAG_VERBOSE) ppl_printf("Authenticating with public key " "\"%.*s\" from agent\r\n", - PTRLEN_PRINTF(s->comment)); + PTRLEN_PRINTF(comment)); /* * Server is willing to accept the key. @@ -746,13 +769,16 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Ask agent for signature. */ agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, s->pk); + put_stringpl(agentreq, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); @@ -774,8 +800,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && (sigblob = get_string(src), !get_err(src))) { ppl_logevent("Sending Pageant's response"); - ssh2_userauth_add_sigblob(s, s->pktout, - s->pk, sigblob); + ssh2_userauth_add_sigblob( + s, s->pktout, + ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob), + sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; } else { @@ -796,14 +825,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } /* Do we have any keys left to try? */ - if (s->pkblob_pos_in_agent) { + if (++s->agent_key_index >= s->agent_key_limit) s->done_agent = true; - s->tried_pubkey_config = true; - } else { - s->keyi++; - if (s->keyi >= s->nkeys) - s->done_agent = true; - } } else if (s->can_pubkey && s->publickey_blob && s->privatekey_available && !s->tried_pubkey_config) {