|
@@ -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;
|
|
@@ -172,6 +177,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);
|
|
@@ -299,8 +311,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.");
|
|
|
|
|
@@ -316,48 +326,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;
|
|
|
+ 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.
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/*
|
|
|
- * 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);
|
|
|
+ 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));
|
|
|
+
|
|
|
+ /* 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;
|
|
|
|
|
|
- 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;
|
|
|
+ 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");
|
|
@@ -456,17 +493,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) {
|
|
|
/*
|
|
@@ -687,7 +714,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.
|
|
@@ -695,16 +723,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(
|
|
@@ -714,8 +733,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;
|
|
|
|
|
@@ -728,11 +749,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.
|
|
@@ -745,13 +768,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);
|
|
@@ -773,8 +799,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 {
|
|
@@ -787,14 +816,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) {
|