r6437 broke the case where Pageant is running but contains no SSH-2 keys
authorjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 13 Nov 2005 16:06:41 +0000 (16:06 +0000)
committerjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 13 Nov 2005 16:06:41 +0000 (16:06 +0000)
that the SSH-2 server is happy with. Fixed, and since I'm here, fix
`pubkeyfile-and-pageant' as well (for SSH-1 and SSH-2).
Also, in SSH-2, we now reexamine "methods that can continue" for every
Pageant key offer, which is technically more correct although it seems
unlikely that it was causing any real problems.
(It's not entirely pretty, but neither was the old code. We could probably
do with some sort of abstraction for public/private keys to avoid carting
lots of fiddly bits of data around.)

git-svn-id: svn://svn.tartarus.org/sgt/putty@6459 cda61777-01e9-0310-a592-d414129be87e

doc/config.but
doc/errors.but
ssh.c

index 0f21078..f1e03b4 100644 (file)
@@ -2393,6 +2393,11 @@ This key must be in PuTTY's native format (\c{*.\i{PPK}}). If you have a
 private key in another format that you want to use with PuTTY, see
 \k{puttygen-conversions}.
 
+If a key file is specified here, and \i{Pageant} is running (see
+\k{pageant}), PuTTY will first try asking Pageant to authenticate with
+that key, and ignore any other keys Pageant may have. If that fails,
+PuTTY will ask for a passphrase as normal.
+
 \H{config-ssh-tty} The TTY panel
 
 The TTY panel lets you configure the remote pseudo-terminal.
index f3fa117..7cdda2f 100644 (file)
@@ -79,16 +79,23 @@ puts up this warning only for \ii{single-DES} and \i{Arcfour} encryption.
 See \k{config-ssh-encryption} for more information on this message.
 
 \H{errors-toomanyauth} \q{Server sent disconnect message type 2
-(SSH_DISCONNECT_PROTOCOL_ERROR): "Too many authentication failures for root"}
+(protocol error): "Too many authentication failures for root"}
 
 This message is produced by an \i{OpenSSH} (or \i{Sun SSH}) server if it
 receives more failed authentication attempts than it is willing to
-tolerate.  This can easily happen if you are using Pageant and have a
-large number of keys loaded into it.  This can be worked around on the
-server by disabling public-key authentication or (for Sun SSH only) by
-increasing \c{MaxAuthTries} in \c{sshd_config}.  Neither of these is a
-really satisfactory solution, and we hope to provide a better one in a
-future version of PuTTY.
+tolerate.
+
+This can easily happen if you are using Pageant and have a
+large number of keys loaded into it, since these servers count each
+offer of a public key as an authentication attempt. This can be worked
+around by specifying the key that's required for the authentication in
+the PuTTY configuration (see \k{config-ssh-privkey}; PuTTY will ignore
+any other keys Pageant may have, but will ask Pageant to do the
+authentication, so that you don't have to type your passphrase.
+
+On the server, this can be worked around by disabling public-key
+authentication or (for Sun SSH only) by increasing \c{MaxAuthTries} in
+\c{sshd_config}.
 
 \H{errors-memory} \q{\ii{Out of memory}}
 
diff --git a/ssh.c b/ssh.c
index 1e22844..3d23aa0 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -3256,13 +3256,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                s->p += 4;
                logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
                for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-                   logeventf(ssh, "Trying Pageant key #%d", s->keyi);
-                   if (s->publickey_blob &&
-                       !memcmp(s->p, s->publickey_blob,
-                               s->publickey_bloblen)) {
-                       logevent("This key matches configured key file");
-                       s->tried_publickey = 1;
-                   }
+                   unsigned char *pkblob = s->p;
                    s->p += 4;
                    {
                        int n, ok = FALSE;
@@ -3295,6 +3289,17 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                            break;
                        }
                    }
+                   if (s->publickey_blob) {
+                       if (!memcmp(pkblob, s->publickey_blob,
+                                   s->publickey_bloblen)) {
+                           logeventf(ssh, "Pageant key #%d matches "
+                                     "configured key file", s->keyi);
+                           s->tried_publickey = 1;
+                       } else
+                           /* Skip non-configured key */
+                           continue;
+                   }
+                   logeventf(ssh, "Trying Pageant key #%d", s->keyi);
                    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
                                PKT_BIGNUM, s->key.modulus, PKT_END);
                    crWaitUntil(pktin);
@@ -3385,6 +3390,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                        break;
                }
                sfree(s->response);
+               if (s->publickey_blob && !s->tried_publickey)
+                   logevent("Configured key file not in Pageant");
            }
            if (s->authed)
                break;
@@ -6485,7 +6492,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        } type;
        int done_service_req;
        int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
-       int tried_pubkey_config, tried_agent;
+       int tried_pubkey_config, done_agent;
        int kbd_inter_refused;
        int we_are_in;
        prompts_t *cur_prompt;
@@ -6498,10 +6505,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int publickey_encrypted;
        char *publickey_algorithm;
        char *publickey_comment;
-       unsigned char request[5], *response, *p;
-       int responselen;
+       unsigned char agent_request[5], *agent_response, *agentp;
+       int agent_responselen;
+       unsigned char *pkblob_in_agent;
        int keyi, nkeys;
-       int authed;
        char *pkblob, *alg, *commentp;
        int pklen, alglen, commentlen;
        int siglen, retlen, len;
@@ -6543,6 +6550,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        }
     }
 
+    /* Arrange to be able to deal with any BANNERs that come in.
+     * (We do this now as packets may come in during the next bit.) */
+    bufchain_init(&ssh->banner);
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
+       ssh2_msg_userauth_banner;
+
     /*
      * Misc one-time setup for authentication.
      */
@@ -6593,6 +6606,68 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            }
        }
 
+       /*
+        * Find out about any keys Pageant has (but if there's a
+        * public key configured, filter out all others).
+        */
+       s->nkeys = 0;
+       s->agent_response = NULL;
+       s->pkblob_in_agent = NULL;
+       if (agent_exists()) {
+
+           void *r;
+
+           logevent("Pageant is running. Requesting keys.");
+
+           /* Request the keys held by the agent. */
+           PUT_32BIT(s->agent_request, 1);
+           s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+           if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
+                            ssh_agent_callback, ssh)) {
+               do {
+                   crReturnV;
+                   if (pktin) {
+                       bombout(("Unexpected data from server while"
+                                " waiting for agent response"));
+                       crStopV;
+                   }
+               } while (pktin || inlen > 0);
+               r = ssh->agent_response;
+               s->agent_responselen = ssh->agent_response_len;
+           }
+           s->agent_response = (unsigned char *) r;
+           if (s->agent_response && s->agent_responselen >= 5 &&
+               s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+               int keyi;
+               unsigned char *p;
+               p = s->agent_response + 5;
+               s->nkeys = GET_32BIT(p);
+               p += 4;
+               logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
+               if (s->publickey_blob) {
+                   /* See if configured key is in agent. */
+                   for (keyi = 0; keyi < s->nkeys; keyi++) {
+                       s->pklen = GET_32BIT(p);
+                       if (s->pklen == s->publickey_bloblen &&
+                           !memcmp(p+4, s->publickey_blob,
+                                   s->publickey_bloblen)) {
+                           logeventf(ssh, "Pageant key #%d matches "
+                                     "configured key file", keyi);
+                           s->keyi = keyi;
+                           s->pkblob_in_agent = p;
+                           break;
+                       }
+                       p += 4 + s->pklen;
+                       p += GET_32BIT(p) + 4; /* comment */
+                   }
+                   if (!s->pkblob_in_agent) {
+                       logevent("Configured key file not in Pageant");
+                       s->nkeys = 0;
+                   }
+               }
+           }
+       }
+
     }
 
     /*
@@ -6621,9 +6696,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     s->username[0] = '\0';
     s->got_username = FALSE;
-    bufchain_init(&ssh->banner);
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
-       ssh2_msg_userauth_banner;
     while (!s->we_are_in) {
        /*
         * Get a username.
@@ -6689,9 +6761,19 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        s->we_are_in = FALSE;
 
        s->tried_pubkey_config = FALSE;
-       s->tried_agent = FALSE;
        s->kbd_inter_refused = FALSE;
 
+       /* Reset agent request state. */
+       s->done_agent = FALSE;
+       if (s->agent_response) {
+           if (s->pkblob_in_agent) {
+               s->agentp = s->pkblob_in_agent;
+           } else {
+               s->agentp = s->agent_response + 5 + 4;
+               s->keyi = 0;
+           }
+       }
+
        while (1) {
            /*
             * Wait for the result of the last authentication request.
@@ -6803,172 +6885,153 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
            ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
 
-           if (s->can_pubkey && agent_exists() && !s->tried_agent) {
+           if (s->can_pubkey && !s->done_agent && s->nkeys) {
 
                /*
-                * Attempt public-key authentication using Pageant.
+                * Attempt public-key authentication using a key from Pageant.
                 */
-               void *r;
-               s->authed = FALSE;
 
                ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
 
-               s->tried_agent = TRUE;
+               logeventf(ssh, "Trying Pageant key #%d", s->keyi);
+
+               /* Unpack key from agent response */
+               s->pklen = GET_32BIT(s->agentp);
+               s->agentp += 4;
+               s->pkblob = (char *)s->agentp;
+               s->agentp += s->pklen;
+               s->alglen = GET_32BIT(s->pkblob);
+               s->alg = s->pkblob + 4;
+               s->commentlen = GET_32BIT(s->agentp);
+               s->agentp += 4;
+               s->commentp = (char *)s->agentp;
+               s->agentp += s->commentlen;
+               /* s->agentp now points at next key, if any */
+
+               /* See if server will accept it */
+               s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+               ssh2_pkt_addstring(s->pktout, s->username);
+               ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                                   /* service requested */
+               ssh2_pkt_addstring(s->pktout, "publickey");
+                                                   /* method */
+               ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */
+               ssh2_pkt_addstring_start(s->pktout);
+               ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+               ssh2_pkt_addstring_start(s->pktout);
+               ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+               ssh2_pkt_send(ssh, s->pktout);
+               s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
 
-               logevent("Pageant is running. Requesting keys.");
+               crWaitUntilV(pktin);
+               if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
 
-               /* Request the keys held by the agent. */
-               PUT_32BIT(s->request, 1);
-               s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-               if (!agent_query(s->request, 5, &r, &s->responselen,
-                                ssh_agent_callback, ssh)) {
-                   do {
-                       crReturnV;
-                       if (pktin) {
-                           bombout(("Unexpected data from server while"
-                                    " waiting for agent response"));
-                           crStopV;
-                       }
-                   } while (pktin || inlen > 0);
-                   r = ssh->agent_response;
-                   s->responselen = ssh->agent_response_len;
-               }
-               s->response = (unsigned char *) r;
-               if (s->response && s->responselen >= 5 &&
-                   s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
-                   s->p = s->response + 5;
-                   s->nkeys = GET_32BIT(s->p);
-                   s->p += 4;
-                   logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
-                   for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-                       void *vret;
+                   /* Offer of key refused. */
+                   s->gotit = TRUE;
 
-                       logeventf(ssh, "Trying Pageant key #%d", s->keyi);
-                       s->pklen = GET_32BIT(s->p);
-                       s->p += 4;
-                       if (s->publickey_blob &&
-                           s->pklen == s->publickey_bloblen &&
-                           !memcmp(s->p, s->publickey_blob,
-                                   s->publickey_bloblen)) {
-                           logevent("This key matches configured key file");
-                           s->tried_pubkey_config = 1;
-                       }
-                       s->pkblob = (char *)s->p;
-                       s->p += s->pklen;
-                       s->alglen = GET_32BIT(s->pkblob);
-                       s->alg = s->pkblob + 4;
-                       s->commentlen = GET_32BIT(s->p);
-                       s->p += 4;
-                       s->commentp = (char *)s->p;
-                       s->p += s->commentlen;
-                       s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
-                       ssh2_pkt_addstring(s->pktout, s->username);
-                       ssh2_pkt_addstring(s->pktout, "ssh-connection");        /* service requested */
-                       ssh2_pkt_addstring(s->pktout, "publickey");     /* method */
-                       ssh2_pkt_addbool(s->pktout, FALSE);     /* no signature included */
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
-                       ssh2_pkt_send(ssh, s->pktout);
-
-                       crWaitUntilV(pktin);
-                       if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
-                           logevent("Key refused");
-                           continue;
-                       }
+               } else {
+                   
+                   void *vret;
 
-                       if (flags & FLAG_VERBOSE) {
-                           c_write_str(ssh, "Authenticating with "
-                                       "public key \"");
-                           c_write(ssh, s->commentp, s->commentlen);
-                           c_write_str(ssh, "\" from agent\r\n");
-                       }
+                   if (flags & FLAG_VERBOSE) {
+                       c_write_str(ssh, "Authenticating with "
+                                   "public key \"");
+                       c_write(ssh, s->commentp, s->commentlen);
+                       c_write_str(ssh, "\" from agent\r\n");
+                   }
 
-                       /*
-                        * Server is willing to accept the key.
-                        * Construct a SIGN_REQUEST.
-                        */
-                       s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
-                       ssh2_pkt_addstring(s->pktout, s->username);
-                       ssh2_pkt_addstring(s->pktout, "ssh-connection");        /* service requested */
-                       ssh2_pkt_addstring(s->pktout, "publickey");     /* method */
-                       ssh2_pkt_addbool(s->pktout, TRUE);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
-
-                       s->siglen = s->pktout->length - 5 + 4 +
-                           ssh->v2_session_id_len;
-                        if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                            s->siglen -= 4;
-                       s->len = 1;       /* message type */
-                       s->len += 4 + s->pklen; /* key blob */
-                       s->len += 4 + s->siglen;        /* data to sign */
-                       s->len += 4;      /* flags */
-                       s->agentreq = snewn(4 + s->len, char);
-                       PUT_32BIT(s->agentreq, s->len);
-                       s->q = s->agentreq + 4;
-                       *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
-                       PUT_32BIT(s->q, s->pklen);
-                       s->q += 4;
-                       memcpy(s->q, s->pkblob, s->pklen);
-                       s->q += s->pklen;
-                       PUT_32BIT(s->q, s->siglen);
+                   /*
+                    * Server is willing to accept the key.
+                    * Construct a SIGN_REQUEST.
+                    */
+                   s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+                   ssh2_pkt_addstring(s->pktout, s->username);
+                   ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                                       /* service requested */
+                   ssh2_pkt_addstring(s->pktout, "publickey");
+                                                       /* method */
+                   ssh2_pkt_addbool(s->pktout, TRUE);  /* signature included */
+                   ssh2_pkt_addstring_start(s->pktout);
+                   ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+                   ssh2_pkt_addstring_start(s->pktout);
+                   ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+
+                   /* Ask agent for signature. */
+                   s->siglen = s->pktout->length - 5 + 4 +
+                       ssh->v2_session_id_len;
+                   if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+                       s->siglen -= 4;
+                   s->len = 1;       /* message type */
+                   s->len += 4 + s->pklen;     /* key blob */
+                   s->len += 4 + s->siglen;    /* data to sign */
+                   s->len += 4;      /* flags */
+                   s->agentreq = snewn(4 + s->len, char);
+                   PUT_32BIT(s->agentreq, s->len);
+                   s->q = s->agentreq + 4;
+                   *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+                   PUT_32BIT(s->q, s->pklen);
+                   s->q += 4;
+                   memcpy(s->q, s->pkblob, s->pklen);
+                   s->q += s->pklen;
+                   PUT_32BIT(s->q, s->siglen);
+                   s->q += 4;
+                   /* Now the data to be signed... */
+                   if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+                       PUT_32BIT(s->q, ssh->v2_session_id_len);
                        s->q += 4;
-                       /* Now the data to be signed... */
-                        if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
-                            PUT_32BIT(s->q, ssh->v2_session_id_len);
-                            s->q += 4;
-                        }
-                       memcpy(s->q, ssh->v2_session_id,
-                              ssh->v2_session_id_len);
-                       s->q += ssh->v2_session_id_len;
-                       memcpy(s->q, s->pktout->data + 5,
-                              s->pktout->length - 5);
-                       s->q += s->pktout->length - 5;
-                       /* And finally the (zero) flags word. */
-                       PUT_32BIT(s->q, 0);
-                       if (!agent_query(s->agentreq, s->len + 4,
-                                        &vret, &s->retlen,
-                                        ssh_agent_callback, ssh)) {
-                           do {
-                               crReturnV;
-                               if (pktin) {
-                                   bombout(("Unexpected data from server"
-                                            " while waiting for agent"
-                                            " response"));
-                                   crStopV;
-                               }
-                           } while (pktin || inlen > 0);
-                           vret = ssh->agent_response;
-                           s->retlen = ssh->agent_response_len;
-                       }
-                       s->ret = vret;
-                       sfree(s->agentreq);
-                       if (s->ret) {
-                           if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
-                               logevent("Sending Pageant's response");
-                               ssh2_add_sigblob(ssh, s->pktout,
-                                                s->pkblob, s->pklen,
-                                                s->ret + 9,
-                                                GET_32BIT(s->ret + 5));
-                               ssh2_pkt_send(ssh, s->pktout);
-                               s->authed = TRUE;
-                               break;
-                           } else {
-                               logevent
-                                   ("Pageant failed to answer challenge");
-                               sfree(s->ret);
+                   }
+                   memcpy(s->q, ssh->v2_session_id,
+                          ssh->v2_session_id_len);
+                   s->q += ssh->v2_session_id_len;
+                   memcpy(s->q, s->pktout->data + 5,
+                          s->pktout->length - 5);
+                   s->q += s->pktout->length - 5;
+                   /* And finally the (zero) flags word. */
+                   PUT_32BIT(s->q, 0);
+                   if (!agent_query(s->agentreq, s->len + 4,
+                                    &vret, &s->retlen,
+                                    ssh_agent_callback, ssh)) {
+                       do {
+                           crReturnV;
+                           if (pktin) {
+                               bombout(("Unexpected data from server"
+                                        " while waiting for agent"
+                                        " response"));
+                               crStopV;
                            }
+                       } while (pktin || inlen > 0);
+                       vret = ssh->agent_response;
+                       s->retlen = ssh->agent_response_len;
+                   }
+                   s->ret = vret;
+                   sfree(s->agentreq);
+                   if (s->ret) {
+                       if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+                           logevent("Sending Pageant's response");
+                           ssh2_add_sigblob(ssh, s->pktout,
+                                            s->pkblob, s->pklen,
+                                            s->ret + 9,
+                                            GET_32BIT(s->ret + 5));
+                           ssh2_pkt_send(ssh, s->pktout);
+                           s->type = AUTH_TYPE_PUBLICKEY;
+                       } else {
+                           /* FIXME: less drastic response */
+                           bombout(("Pageant failed to answer challenge"));
+                           crStopV;
                        }
                    }
-                   if (s->authed)
-                       continue;
                }
-               sfree(s->response);
+
+               /* Do we have any keys left to try? */
+               if (s->pkblob_in_agent) {
+                   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->tried_pubkey_config) {
@@ -7513,6 +7576,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        sfree(s->publickey_blob);
        sfree(s->publickey_comment);
     }
+    if (s->agent_response)
+       sfree(s->agent_response);
 
     /*
      * Now the connection protocol has started, one way or another.