|
@@ -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;
|
|
|
|
|
@@ -71,9 +76,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;
|
|
@@ -122,6 +127,7 @@ static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = {
|
|
|
ssh2_userauth_want_user_input,
|
|
|
ssh2_userauth_got_user_input,
|
|
|
ssh2_userauth_reconfigure,
|
|
|
+ ssh_ppl_default_queued_data_size,
|
|
|
"ssh-userauth",
|
|
|
};
|
|
|
|
|
@@ -177,6 +183,14 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl)
|
|
|
if (s->successor_layer)
|
|
|
ssh_ppl_free(s->successor_layer);
|
|
|
|
|
|
+ if (s->agent_keys) {
|
|
|
+ size_t i; // WINSCP
|
|
|
+ for (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);
|
|
@@ -305,8 +319,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.");
|
|
|
|
|
@@ -322,48 +334,83 @@ 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;
|
|
|
+ size_t nkeys = get_uint32(s->asrc);
|
|
|
+ size_t origpos = s->asrc->pos;
|
|
|
|
|
|
- s->nkeys = toint(get_uint32(s->asrc));
|
|
|
+ /*
|
|
|
+ * Check that the agent response is well formed.
|
|
|
+ */
|
|
|
+ { // WINSCP
|
|
|
+ size_t i; // WINSCP
|
|
|
+ for (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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } // WINSCP
|
|
|
|
|
|
/*
|
|
|
- * Vet the Pageant response to ensure that the key count
|
|
|
- * and blob lengths make sense.
|
|
|
+ * Copy the list of public-key blobs out of the Pageant
|
|
|
+ * response.
|
|
|
*/
|
|
|
- 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);
|
|
|
-
|
|
|
- /* 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;
|
|
|
- }
|
|
|
+ BinarySource_REWIND_TO(s->asrc, origpos);
|
|
|
+ s->agent_keys_len = nkeys;
|
|
|
+ s->agent_keys = snewn(s->agent_keys_len, agent_key);
|
|
|
+ { // WINSCP
|
|
|
+ size_t i; // WINSCP
|
|
|
+ for (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;
|
|
|
+ { // WINSCP
|
|
|
+ /* 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);
|
|
|
+ } // WINSCP
|
|
|
+ }
|
|
|
+ } // WINSCP
|
|
|
+
|
|
|
+ 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");
|
|
@@ -438,7 +485,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
}
|
|
|
sfree(s->locally_allocated_username); /* for change_username */
|
|
|
s->username = s->locally_allocated_username =
|
|
|
- dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
+ prompt_get_result(s->cur_prompt->prompts[0]);
|
|
|
free_prompts(s->cur_prompt);
|
|
|
} else {
|
|
|
if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
|
|
@@ -462,17 +509,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) {
|
|
|
/*
|
|
@@ -517,9 +554,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
ptrlen data = bufchain_prefix(&s->banner);
|
|
|
seat_stderr_pl(s->ppl.seat, data);
|
|
|
display_banner(s->ppl.seat, data.ptr, data.len); // WINSCP
|
|
|
- bufchain_consume(&s->banner, data.len);
|
|
|
mid_line =
|
|
|
(((const char *)data.ptr)[data.len-1] != '\n');
|
|
|
+ bufchain_consume(&s->banner, data.len);
|
|
|
}
|
|
|
bufchain_clear(&s->banner);
|
|
|
|
|
@@ -629,7 +666,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
/*
|
|
|
* Save the methods string for use in error messages.
|
|
|
*/
|
|
|
- s->last_methods_string->len = 0;
|
|
|
+ strbuf_clear(s->last_methods_string);
|
|
|
put_datapl(s->last_methods_string, methods);
|
|
|
#ifdef WINSCP
|
|
|
ppl_logevent("Server offered these authentication methods: %s", s->last_methods_string->s);
|
|
@@ -702,7 +739,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.
|
|
@@ -710,16 +748,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(
|
|
@@ -729,8 +758,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;
|
|
|
|
|
@@ -743,11 +774,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.
|
|
@@ -760,13 +793,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);
|
|
@@ -788,8 +824,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 {
|
|
@@ -797,19 +836,21 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
ppl_printf("Pageant failed to "
|
|
|
"provide a signature\r\n");
|
|
|
s->suppress_wait_for_response_packet = true;
|
|
|
+ ssh_free_pktout(s->pktout);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ ppl_logevent("Pageant failed to respond to "
|
|
|
+ "signing request");
|
|
|
+ ppl_printf("Pageant failed to "
|
|
|
+ "respond to signing request\r\n");
|
|
|
+ s->suppress_wait_for_response_packet = true;
|
|
|
+ ssh_free_pktout(s->pktout);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 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) {
|
|
@@ -899,7 +940,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
return;
|
|
|
}
|
|
|
passphrase =
|
|
|
- dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
+ prompt_get_result(s->cur_prompt->prompts[0]);
|
|
|
free_prompts(s->cur_prompt);
|
|
|
} else {
|
|
|
passphrase = NULL; /* no passphrase needed */
|
|
@@ -1360,6 +1401,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
}
|
|
|
if (sb->len)
|
|
|
s->cur_prompt->instruction = strbuf_to_str(sb);
|
|
|
+ else
|
|
|
+ strbuf_free(sb);
|
|
|
|
|
|
/*
|
|
|
* Our prompts_t is fully constructed now. Get the
|
|
@@ -1400,10 +1443,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
|
|
|
put_uint32(s->pktout, s->num_prompts);
|
|
|
{ // WINSCP
|
|
|
- uint32_t i;
|
|
|
- for (i=0; i < s->num_prompts; i++) {
|
|
|
- put_stringz(s->pktout,
|
|
|
- s->cur_prompt->prompts[i]->result);
|
|
|
+ uint32_t i; // WINSCP
|
|
|
+ for (i = 0; i < s->num_prompts; i++) {
|
|
|
+ put_stringz(s->pktout, prompt_get_result_ref(
|
|
|
+ s->cur_prompt->prompts[i]));
|
|
|
}
|
|
|
} // WINSCP
|
|
|
s->pktout->minlen = 256;
|
|
@@ -1496,7 +1539,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
* Squirrel away the password. (We may need it later if
|
|
|
* asked to change it.)
|
|
|
*/
|
|
|
- s->password = dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
+ s->password = prompt_get_result(s->cur_prompt->prompts[0]);
|
|
|
free_prompts(s->cur_prompt);
|
|
|
|
|
|
/*
|
|
@@ -1627,20 +1670,20 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
* (A side effect is that the user doesn't have to
|
|
|
* re-enter it if they louse up the new password.)
|
|
|
*/
|
|
|
- if (s->cur_prompt->prompts[0]->result[0]) {
|
|
|
+ if (s->cur_prompt->prompts[0]->result->s[0]) {
|
|
|
smemclr(s->password, strlen(s->password));
|
|
|
/* burn the evidence */
|
|
|
sfree(s->password);
|
|
|
- s->password =
|
|
|
- dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
+ s->password = prompt_get_result(
|
|
|
+ s->cur_prompt->prompts[0]);
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* Check the two new passwords match.
|
|
|
*/
|
|
|
- got_new = (strcmp(s->cur_prompt->prompts[1]->result,
|
|
|
- s->cur_prompt->prompts[2]->result)
|
|
|
- == 0);
|
|
|
+ got_new = !strcmp(
|
|
|
+ prompt_get_result_ref(s->cur_prompt->prompts[1]),
|
|
|
+ prompt_get_result_ref(s->cur_prompt->prompts[2]));
|
|
|
if (!got_new)
|
|
|
/* They don't. Silly user. */
|
|
|
ppl_printf("Passwords do not match\r\n");
|
|
@@ -1658,8 +1701,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
|
|
put_stringz(s->pktout, "password");
|
|
|
put_bool(s->pktout, true);
|
|
|
put_stringz(s->pktout, s->password);
|
|
|
- put_stringz(s->pktout,
|
|
|
- s->cur_prompt->prompts[1]->result);
|
|
|
+ put_stringz(s->pktout, prompt_get_result_ref(
|
|
|
+ s->cur_prompt->prompts[1]));
|
|
|
free_prompts(s->cur_prompt);
|
|
|
s->pktout->minlen = 256;
|
|
|
pq_push(s->ppl.out_pq, s->pktout);
|
|
@@ -1821,7 +1864,7 @@ static void ssh2_userauth_add_sigblob(
|
|
|
/* debug("modulus length is %d\n", len); */
|
|
|
/* debug("signature length is %d\n", siglen); */
|
|
|
|
|
|
- if (mod_mp.len != sig_mp.len) {
|
|
|
+ if (mod_mp.len > sig_mp.len) {
|
|
|
strbuf *substr = strbuf_new();
|
|
|
put_data(substr, sigblob.ptr, sig_prefix_len);
|
|
|
put_uint32(substr, mod_mp.len);
|