]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Pageant: factor out cross-platform parts of add_keyfile().
authorSimon Tatham <anakin@pobox.com>
Mon, 11 May 2015 14:06:25 +0000 (15:06 +0100)
committerSimon Tatham <anakin@pobox.com>
Mon, 11 May 2015 14:49:09 +0000 (15:49 +0100)
I've now centralised into pageant.c all the logic about trying to load
keys of any type, with no passphrase or with the passphrases used in
previous key-loading actions or with a new user-supplied passphrase,
whether we're the main Pageant process ourself or are talking to
another one as a client. The only part of that code remaining in
winpgnt.c is the user interaction via dialog boxes, which of course is
the part that will need to be done differently on other platforms.

pageant.c
pageant.h
windows/winpgnt.c

index a97110d09c2990434659128ff36061709e89f2c1..76e5911aab83e36a6f2d6d0b89e0c16a51b45400 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -27,6 +27,8 @@ int random_byte(void)
     return 0;                 /* unreachable, but placate optimiser */
 }
 
+static int pageant_local = FALSE;
+
 /*
  * rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys.
  */
@@ -918,6 +920,7 @@ void *pageant_failure_msg(int *outlen)
 
 void pageant_init(void)
 {
+    pageant_local = TRUE;
     rsakeys = newtree234(cmpkeys_rsa);
     ssh2keys = newtree234(cmpkeys_ssh2);
 }
@@ -1173,3 +1176,409 @@ void pageant_listener_free(struct pageant_listen_state *pl)
         sk_close(pl->listensock);
     sfree(pl);
 }
+
+/* ----------------------------------------------------------------------
+ * Code to perform agent operations either as a client, or within the
+ * same process as the running agent.
+ */
+
+static tree234 *passphrases = NULL;
+
+/*
+ * After processing a list of filenames, we want to forget the
+ * passphrases.
+ */
+void pageant_forget_passphrases(void)
+{
+    while (count234(passphrases) > 0) {
+       char *pp = index234(passphrases, 0);
+       smemclr(pp, strlen(pp));
+       delpos234(passphrases, 0);
+       free(pp);
+    }
+}
+
+void *pageant_get_keylist1(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen, retval;
+       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+       PUT_32BIT(request, 4);
+
+       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
+       assert(retval == 1);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist1(length);
+    }
+    return ret;
+}
+
+void *pageant_get_keylist2(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen, retval;
+
+       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+       PUT_32BIT(request, 4);
+
+       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
+       assert(retval == 1);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist2(length);
+    }
+    return ret;
+}
+
+int pageant_add_keyfile(Filename *filename, const char *passphrase,
+                        char **retstr)
+{
+    struct RSAKey *rkey = NULL;
+    struct ssh2_userkey *skey = NULL;
+    int needs_pass;
+    int ret;
+    int attempts;
+    char *comment;
+    const char *this_passphrase;
+    const char *error = NULL;
+    int type;
+
+    if (!passphrases) {
+        passphrases = newtree234(NULL);
+    }
+
+    *retstr = NULL;
+
+    type = key_type(filename);
+    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
+       *retstr = dupprintf("Couldn't load this key (%s)",
+                            key_type_to_str(type));
+       return PAGEANT_ACTION_FAILURE;
+    }
+
+    /*
+     * See if the key is already loaded (in the primary Pageant,
+     * which may or may not be us).
+     */
+    {
+       void *blob;
+       unsigned char *keylist, *p;
+       int i, nkeys, bloblen, keylistlen;
+
+       if (type == SSH_KEYTYPE_SSH1) {
+           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+                return PAGEANT_ACTION_FAILURE;
+           }
+           keylist = pageant_get_keylist1(&keylistlen);
+       } else {
+           unsigned char *blob2;
+           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
+                                       NULL, &error);
+           if (!blob) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           /* For our purposes we want the blob prefixed with its length */
+           blob2 = snewn(bloblen+4, unsigned char);
+           PUT_32BIT(blob2, bloblen);
+           memcpy(blob2 + 4, blob, bloblen);
+           sfree(blob);
+           blob = blob2;
+
+           keylist = pageant_get_keylist2(&keylistlen);
+       }
+       if (keylist) {
+           if (keylistlen < 4) {
+               *retstr = dupstr("Received broken key list from agent");
+               return PAGEANT_ACTION_FAILURE;
+           }
+           nkeys = toint(GET_32BIT(keylist));
+           if (nkeys < 0) {
+               *retstr = dupstr("Received broken key list from agent");
+               return PAGEANT_ACTION_FAILURE;
+           }
+           p = keylist + 4;
+           keylistlen -= 4;
+
+           for (i = 0; i < nkeys; i++) {
+               if (!memcmp(blob, p, bloblen)) {
+                   /* Key is already present; we can now leave. */
+                   sfree(keylist);
+                   sfree(blob);
+                    return PAGEANT_ACTION_OK;
+               }
+               /* Now skip over public blob */
+               if (type == SSH_KEYTYPE_SSH1) {
+                   int n = rsa_public_blob_len(p, keylistlen);
+                   if (n < 0) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               } else {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+               /* Now skip over comment field */
+               {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+           }
+
+           sfree(keylist);
+       }
+
+       sfree(blob);
+    }
+
+    error = NULL;
+    if (type == SSH_KEYTYPE_SSH1)
+       needs_pass = rsakey_encrypted(filename, &comment);
+    else
+       needs_pass = ssh2_userkey_encrypted(filename, &comment);
+    attempts = 0;
+    if (type == SSH_KEYTYPE_SSH1)
+       rkey = snew(struct RSAKey);
+
+    /*
+     * Loop round repeatedly trying to load the key, until we either
+     * succeed, fail for some serious reason, or run out of
+     * passphrases to try.
+     */
+    while (1) {
+       if (needs_pass) {
+
+            /*
+             * If we've been given a passphrase on input, try using
+             * it. Otherwise, try one from our tree234 of previously
+             * useful passphrases.
+             */
+            if (passphrase) {
+                this_passphrase = (attempts == 0 ? passphrase : NULL);
+            } else {
+                this_passphrase = (const char *)index234(passphrases, attempts);
+            }
+
+            if (!this_passphrase) {
+                /*
+                 * Run out of passphrases to try.
+                 */
+                *retstr = comment;
+                return PAGEANT_ACTION_NEED_PP;
+            }
+       } else
+           this_passphrase = "";
+
+       if (type == SSH_KEYTYPE_SSH1)
+           ret = loadrsakey(filename, rkey, this_passphrase, &error);
+       else {
+           skey = ssh2_load_userkey(filename, this_passphrase, &error);
+           if (skey == SSH2_WRONG_PASSPHRASE)
+               ret = -1;
+           else if (!skey)
+               ret = 0;
+           else
+               ret = 1;
+       }
+
+        if (ret == 0) {
+            /*
+             * Failed to load the key file, for some reason other than
+             * a bad passphrase.
+             */
+            *retstr = dupstr(error);
+            return PAGEANT_ACTION_FAILURE;
+        } else if (ret == 1) {
+            /*
+             * Successfully loaded the key file.
+             */
+            break;
+        } else {
+            /*
+             * Passphrase wasn't right; go round again.
+             */
+            attempts++;
+        }
+    }
+
+    /*
+     * If we get here, we've succesfully loaded the key into
+     * rkey/skey, but not yet added it to the agent.
+     */
+
+    /*
+     * If the key was successfully decrypted, save the passphrase for
+     * use with other keys we try to load.
+     */
+    {
+        char *pp_copy = dupstr(this_passphrase);
+       if (addpos234(passphrases, pp_copy, 0) != pp_copy) {
+            /* No need; it was already there. */
+            smemclr(pp_copy, strlen(pp_copy));
+            sfree(pp_copy);
+        }
+    }
+
+    if (comment)
+       sfree(comment);
+
+    if (type == SSH_KEYTYPE_SSH1) {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, clen, resplen, ret;
+
+           clen = strlen(rkey->comment);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 +                    /* bit count */
+               ssh1_bignum_length(rkey->modulus) +
+               ssh1_bignum_length(rkey->exponent) +
+               ssh1_bignum_length(rkey->private_exponent) +
+               ssh1_bignum_length(rkey->iqmp) +
+               ssh1_bignum_length(rkey->p) +
+               ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
+           reqlen += 4;
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
+           reqlen +=
+               ssh1_write_bignum(request + reqlen,
+                                 rkey->private_exponent);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, rkey->comment, clen);
+           reqlen += 4 + clen;
+           PUT_32BIT(request, reqlen - 4);
+
+           ret = agent_query(request, reqlen, &vresponse, &resplen,
+                             NULL, NULL);
+           assert(ret == 1);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                return PAGEANT_ACTION_FAILURE;
+            }
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh1_key(rkey)) {
+               sfree(rkey);           /* already present, don't waste RAM */
+            }
+       }
+    } else {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, alglen, clen, keybloblen, resplen, ret;
+           alglen = strlen(skey->alg->name);
+           clen = strlen(skey->comment);
+
+           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 + alglen +           /* algorithm name */
+               keybloblen +           /* key data */
+               4 + clen               /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH2_AGENTC_ADD_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, alglen);
+           reqlen += 4;
+           memcpy(request + reqlen, skey->alg->name, alglen);
+           reqlen += alglen;
+           reqlen += skey->alg->openssh_fmtkey(skey->data,
+                                               request + reqlen,
+                                               keybloblen);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, skey->comment, clen);
+           reqlen += clen + 4;
+           PUT_32BIT(request, reqlen - 4);
+
+           ret = agent_query(request, reqlen, &vresponse, &resplen,
+                             NULL, NULL);
+           assert(ret == 1);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                return PAGEANT_ACTION_FAILURE;
+            }
+
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh2_key(skey)) {
+               skey->alg->freekey(skey->data);
+               sfree(skey);           /* already present, don't waste RAM */
+           }
+       }
+    }
+    return PAGEANT_ACTION_OK;
+}
index 06dd033a8010fd6cd8d412afa6cd388c8db46657..c1bc854ebe2e315693271d797f2357bc3363782f 100644 (file)
--- a/pageant.h
+++ b/pageant.h
@@ -85,3 +85,38 @@ void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock);
 void pageant_listener_set_logfn(struct pageant_listen_state *pl,
                                 void *logctx, pageant_logfn_t logfn);
 void pageant_listener_free(struct pageant_listen_state *pl);
+
+/*
+ * Functions to perform specific key actions, either as a client of an
+ * ssh-agent running elsewhere, or directly on the agent state in this
+ * process. (On at least one platform we want to do this in an
+ * agnostic way between the two situations.)
+ *
+ * pageant_get_keylist{1,2} work just like pageant_make_keylist{1,2}
+ * above, except that they can also cope if they have to contact an
+ * external agent.
+ *
+ * pageant_add_keyfile() is used to load a private key from a file and
+ * add it to the agent. Initially, you should call it with passphrase
+ * NULL, and it will check if the key is already in the agent, and
+ * whether a passphrase is required. Return values are given in the
+ * enum below. On return, *retstr will either be NULL, or a
+ * dynamically allocated string containing a key comment or an error
+ * message.
+ *
+ * pageant_add_keyfile() also remembers passphrases with which it's
+ * successfully decrypted keys (because if you try to add multiple
+ * keys in one go, you might very well have used the same passphrase
+ * for keys that have the same trust properties). Call
+ * pageant_forget_passphrases() to get rid of them all.
+ */
+void *pageant_get_keylist1(int *length);
+void *pageant_get_keylist2(int *length);
+enum {
+    PAGEANT_ACTION_OK,       /* success; no further action needed */
+    PAGEANT_ACTION_FAILURE,  /* failure; *retstr is error message */
+    PAGEANT_ACTION_NEED_PP   /* need passphrase: *retstr is key comment */
+};
+int pageant_add_keyfile(Filename *filename, const char *passphrase,
+                        char **retstr);
+void pageant_forget_passphrases(void);
index 2aa9886081da7d0727b5639d17673a65f6abe41f..a82888f6755110c35ca07e415874970b81780302 100644 (file)
@@ -112,33 +112,11 @@ static void unmungestr(char *in, char *out, int outlen)
 
 static int has_security;
 
-/*
- * Forward references
- */
-static void *get_keylist1(int *length);
-static void *get_keylist2(int *length);
-
 struct PassphraseProcStruct {
     char **passphrase;
     char *comment;
 };
 
-static tree234 *passphrases = NULL;
-
-/* 
- * After processing a list of filenames, we want to forget the
- * passphrases.
- */
-static void forget_passphrases(void)
-{
-    while (count234(passphrases) > 0) {
-       char *pp = index234(passphrases, 0);
-       smemclr(pp, strlen(pp));
-       delpos234(passphrases, 0);
-       free(pp);
-    }
-}
-
 /*
  * Dialog-box function for the Licence box.
  */
@@ -340,397 +318,6 @@ void keylist_update(void)
     }
 }
 
-/*
- * This function loads a key from a file and adds it.
- */
-static void add_keyfile(Filename *filename)
-{
-    char *passphrase;
-    struct RSAKey *rkey = NULL;
-    struct ssh2_userkey *skey = NULL;
-    int needs_pass;
-    int ret;
-    int attempts;
-    char *comment;
-    const char *error = NULL;
-    int type;
-    int original_pass;
-       
-    type = key_type(filename);
-    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
-       char *msg = dupprintf("Couldn't load this key (%s)",
-                             key_type_to_str(type));
-       message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                   HELPCTXID(errors_cantloadkey));
-       sfree(msg);
-       return;
-    }
-
-    /*
-     * See if the key is already loaded (in the primary Pageant,
-     * which may or may not be us).
-     */
-    {
-       void *blob;
-       unsigned char *keylist, *p;
-       int i, nkeys, bloblen, keylistlen;
-
-       if (type == SSH_KEYTYPE_SSH1) {
-           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
-               char *msg = dupprintf("Couldn't load private key (%s)", error);
-               message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                           HELPCTXID(errors_cantloadkey));
-               sfree(msg);
-               return;
-           }
-           keylist = get_keylist1(&keylistlen);
-       } else {
-           unsigned char *blob2;
-           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
-                                       NULL, &error);
-           if (!blob) {
-               char *msg = dupprintf("Couldn't load private key (%s)", error);
-               message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                           HELPCTXID(errors_cantloadkey));
-               sfree(msg);
-               return;
-           }
-           /* For our purposes we want the blob prefixed with its length */
-           blob2 = snewn(bloblen+4, unsigned char);
-           PUT_32BIT(blob2, bloblen);
-           memcpy(blob2 + 4, blob, bloblen);
-           sfree(blob);
-           blob = blob2;
-
-           keylist = get_keylist2(&keylistlen);
-       }
-       if (keylist) {
-           if (keylistlen < 4) {
-               MessageBox(NULL, "Received broken key list?!", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           nkeys = toint(GET_32BIT(keylist));
-           if (nkeys < 0) {
-               MessageBox(NULL, "Received broken key list?!", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           p = keylist + 4;
-           keylistlen -= 4;
-
-           for (i = 0; i < nkeys; i++) {
-               if (!memcmp(blob, p, bloblen)) {
-                   /* Key is already present; we can now leave. */
-                   sfree(keylist);
-                   sfree(blob);
-                   return;
-               }
-               /* Now skip over public blob */
-               if (type == SSH_KEYTYPE_SSH1) {
-                   int n = rsa_public_blob_len(p, keylistlen);
-                   if (n < 0) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               } else {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = toint(4 + GET_32BIT(p));
-                   if (n < 0 || keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-               /* Now skip over comment field */
-               {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = toint(4 + GET_32BIT(p));
-                   if (n < 0 || keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-           }
-
-           sfree(keylist);
-       }
-
-       sfree(blob);
-    }
-
-    error = NULL;
-    if (type == SSH_KEYTYPE_SSH1)
-       needs_pass = rsakey_encrypted(filename, &comment);
-    else
-       needs_pass = ssh2_userkey_encrypted(filename, &comment);
-    attempts = 0;
-    if (type == SSH_KEYTYPE_SSH1)
-       rkey = snew(struct RSAKey);
-    passphrase = NULL;
-    original_pass = 0;
-    do {
-        burnstr(passphrase);
-        passphrase = NULL;
-
-       if (needs_pass) {
-           /* try all the remembered passphrases first */
-           char *pp = index234(passphrases, attempts);
-           if(pp) {
-               passphrase = dupstr(pp);
-           } else {
-               int dlgret;
-                struct PassphraseProcStruct pps;
-
-                pps.passphrase = &passphrase;
-                pps.comment = comment;
-
-               original_pass = 1;
-               dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
-                                       NULL, PassphraseProc, (LPARAM) &pps);
-               passphrase_box = NULL;
-               if (!dlgret) {
-                   if (comment)
-                       sfree(comment);
-                   if (type == SSH_KEYTYPE_SSH1)
-                       sfree(rkey);
-                   return;                    /* operation cancelled */
-               }
-
-                assert(passphrase != NULL);
-           }
-       } else
-           passphrase = dupstr("");
-
-       if (type == SSH_KEYTYPE_SSH1)
-           ret = loadrsakey(filename, rkey, passphrase, &error);
-       else {
-           skey = ssh2_load_userkey(filename, passphrase, &error);
-           if (skey == SSH2_WRONG_PASSPHRASE)
-               ret = -1;
-           else if (!skey)
-               ret = 0;
-           else
-               ret = 1;
-       }
-       attempts++;
-    } while (ret == -1);
-
-    if(original_pass && ret) {
-        /* If they typed in an ok passphrase, remember it */
-       addpos234(passphrases, passphrase, 0);
-    } else {
-        /* Otherwise, destroy it */
-        burnstr(passphrase);
-    }
-    passphrase = NULL;
-
-    if (comment)
-       sfree(comment);
-    if (ret == 0) {
-       char *msg = dupprintf("Couldn't load private key (%s)", error);
-       message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                   HELPCTXID(errors_cantloadkey));
-       sfree(msg);
-       if (type == SSH_KEYTYPE_SSH1)
-           sfree(rkey);
-       return;
-    }
-    if (type == SSH_KEYTYPE_SSH1) {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, clen, resplen, ret;
-
-           clen = strlen(rkey->comment);
-
-           reqlen = 4 + 1 +           /* length, message type */
-               4 +                    /* bit count */
-               ssh1_bignum_length(rkey->modulus) +
-               ssh1_bignum_length(rkey->exponent) +
-               ssh1_bignum_length(rkey->private_exponent) +
-               ssh1_bignum_length(rkey->iqmp) +
-               ssh1_bignum_length(rkey->p) +
-               ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
-               ;
-
-           request = snewn(reqlen, unsigned char);
-
-           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
-           reqlen = 5;
-           PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
-           reqlen += 4;
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
-           reqlen +=
-               ssh1_write_bignum(request + reqlen,
-                                 rkey->private_exponent);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
-           PUT_32BIT(request + reqlen, clen);
-           memcpy(request + reqlen + 4, rkey->comment, clen);
-           reqlen += 4 + clen;
-           PUT_32BIT(request, reqlen - 4);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               MessageBox(NULL, "The already running Pageant "
-                          "refused to add the key.", APPNAME,
-                          MB_OK | MB_ICONERROR);
-
-           sfree(request);
-           sfree(response);
-       } else {
-           if (!pageant_add_ssh1_key(rkey))
-               sfree(rkey);           /* already present, don't waste RAM */
-       }
-    } else {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, alglen, clen, keybloblen, resplen, ret;
-           alglen = strlen(skey->alg->name);
-           clen = strlen(skey->comment);
-
-           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
-
-           reqlen = 4 + 1 +           /* length, message type */
-               4 + alglen +           /* algorithm name */
-               keybloblen +           /* key data */
-               4 + clen               /* comment */
-               ;
-
-           request = snewn(reqlen, unsigned char);
-
-           request[4] = SSH2_AGENTC_ADD_IDENTITY;
-           reqlen = 5;
-           PUT_32BIT(request + reqlen, alglen);
-           reqlen += 4;
-           memcpy(request + reqlen, skey->alg->name, alglen);
-           reqlen += alglen;
-           reqlen += skey->alg->openssh_fmtkey(skey->data,
-                                               request + reqlen,
-                                               keybloblen);
-           PUT_32BIT(request + reqlen, clen);
-           memcpy(request + reqlen + 4, skey->comment, clen);
-           reqlen += clen + 4;
-           PUT_32BIT(request, reqlen - 4);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               MessageBox(NULL, "The already running Pageant "
-                          "refused to add the key.", APPNAME,
-                          MB_OK | MB_ICONERROR);
-
-           sfree(request);
-           sfree(response);
-       } else {
-           if (!pageant_add_ssh2_key(skey)) {
-               skey->alg->freekey(skey->data);
-               sfree(skey);           /* already present, don't waste RAM */
-           }
-       }
-    }
-}
-
-/*
- * Acquire a keylist1 from the primary Pageant; this means either
- * calling pageant_make_keylist1 (if that's us) or sending a message
- * to the primary Pageant (if it's not).
- */
-static void *get_keylist1(int *length)
-{
-    void *ret;
-
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
-            sfree(response);
-           return NULL;
-        }
-
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
-
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = pageant_make_keylist1(length);
-    }
-    return ret;
-}
-
-/*
- * Acquire a keylist2 from the primary Pageant; this means either
- * calling pageant_make_keylist2 (if that's us) or sending a message
- * to the primary Pageant (if it's not).
- */
-static void *get_keylist2(int *length)
-{
-    void *ret;
-
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-
-       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
-            sfree(response);
-           return NULL;
-        }
-
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
-
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = pageant_make_keylist2(length);
-    }
-    return ret;
-}
-
 static void answer_msg(void *msgv)
 {
     unsigned char *msg = (unsigned char *)msgv;
@@ -759,6 +346,69 @@ static void answer_msg(void *msgv)
     sfree(reply);
 }
 
+static void win_add_keyfile(Filename *filename)
+{
+    char *err;
+    int ret;
+    char *passphrase = NULL;
+
+    /*
+     * Try loading the key without a passphrase. (Or rather, without a
+     * _new_ passphrase; pageant_add_keyfile will take care of trying
+     * all the passphrases we've already stored.)
+     */
+    ret = pageant_add_keyfile(filename, NULL, &err);
+    if (ret == PAGEANT_ACTION_OK) {
+        goto done;
+    } else if (ret == PAGEANT_ACTION_FAILURE) {
+        goto error;
+    }
+
+    /*
+     * OK, a passphrase is needed, and we've been given the key
+     * comment to use in the passphrase prompt.
+     */
+    while (1) {
+        int dlgret;
+        struct PassphraseProcStruct pps;
+
+        pps.passphrase = &passphrase;
+        pps.comment = err;
+        dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
+                                NULL, PassphraseProc, (LPARAM) &pps);
+        passphrase_box = NULL;
+
+        sfree(err);
+
+        if (!dlgret)
+            goto done;                /* operation cancelled */
+
+        assert(passphrase != NULL);
+
+        ret = pageant_add_keyfile(filename, passphrase, &err);
+        if (ret == PAGEANT_ACTION_OK) {
+            goto done;
+        } else if (ret == PAGEANT_ACTION_FAILURE) {
+            goto error;
+        }
+
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
+        passphrase = NULL;
+    }
+
+  error:
+    message_box(err, APPNAME, MB_OK | MB_ICONERROR,
+                HELPCTXID(errors_cantloadkey));
+  done:
+    if (passphrase) {
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
+    }
+    sfree(err);
+    return;
+}
+
 /*
  * Prompt for a key file to add, and add it.
  */
@@ -783,7 +433,7 @@ static void prompt_add_keyfile(void)
        if(strlen(filelist) > of.nFileOffset) {
            /* Only one filename returned? */
             Filename *fn = filename_from_str(filelist);
-           add_keyfile(fn);
+           win_add_keyfile(fn);
             filename_free(fn);
         } else {
            /* we are returned a bunch of strings, end to
@@ -796,7 +446,7 @@ static void prompt_add_keyfile(void)
            while (*filewalker != '\0') {
                char *filename = dupcat(dir, "\\", filewalker, NULL);
                 Filename *fn = filename_from_str(filename);
-               add_keyfile(fn);
+               win_add_keyfile(fn);
                 filename_free(fn);
                sfree(filename);
                filewalker += strlen(filewalker) + 1;
@@ -804,7 +454,7 @@ static void prompt_add_keyfile(void)
        }
 
        keylist_update();
-       forget_passphrases();
+       pageant_forget_passphrases();
     }
     sfree(filelist);
 }
@@ -1453,11 +1103,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
         pageant_init();
     }
 
-    /*
-     * Initialise storage for short-term passphrase cache.
-     */
-    passphrases = newtree234(NULL);
-
     /*
      * Process the command line and add keys as listed on it.
      */
@@ -1479,7 +1124,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            break;
        } else {
             Filename *fn = filename_from_str(argv[i]);
-           add_keyfile(fn);
+           win_add_keyfile(fn);
             filename_free(fn);
            added_keys = TRUE;
        }
@@ -1489,7 +1134,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * Forget any passphrase that we retained while going over
      * command line keyfiles.
      */
-    forget_passphrases();
+    pageant_forget_passphrases();
 
     if (command) {
        char *args;