]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - pageant.c
first pass
[PuTTY.git] / pageant.c
index 33d0d30594bbc30c25453b4f60c9dffe9729f0b0..2d9a740236739923800f4d2cfc79a540302c5fdb 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.
  */
@@ -257,25 +259,6 @@ void *pageant_make_keylist2(int *length)
     return ret;
 }
 
-char *fingerprint_ssh2_blob(const void *blob, int bloblen)
-{
-    unsigned char digest[16];
-    char fingerprint_str[16*3];
-    unsigned stringlen;
-    int i;
-
-    MD5Simple(blob, bloblen, digest);
-    for (i = 0; i < 16; i++)
-        sprintf(fingerprint_str + i*3, "%02x%s", digest[i], i==15 ? "" : ":");
-
-    stringlen = GET_32BIT((const unsigned char *)blob);
-    if (stringlen < bloblen-4)
-        return dupprintf("%.*s %s", (int)stringlen, (const char *)blob + 4,
-                         fingerprint_str);
-    else
-        return dupstr(fingerprint_str);        
-}
-
 static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
 #ifdef __GNUC__
 __attribute__ ((format (printf, 3, 4)))
@@ -379,7 +362,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
                 int i;
                 struct ssh2_userkey *skey;
                 for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
-                    char *fingerprint = skey->alg->fingerprint(skey->data);
+                    char *fingerprint = ssh2_fingerprint(skey->alg,
+                                                         skey->data);
                     plog(logctx, logfn, "returned key: %s %s",
                          fingerprint, skey->comment);
                     sfree(fingerprint);
@@ -422,6 +406,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
            if (i < 0) {
                 freebn(reqkey.exponent);
                 freebn(reqkey.modulus);
+               freebn(challenge);
                 fail_reason = "request truncated before challenge";
                goto failure;
             }
@@ -526,7 +511,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
             }
            data = p;
             if (logfn) {
-                char *fingerprint = fingerprint_ssh2_blob(b.blob, b.len);
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
                 plog(logctx, logfn, "requested key: %s", fingerprint);
                 sfree(fingerprint);
             }
@@ -682,25 +667,15 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
            p += alglen;
 
            key = snew(struct ssh2_userkey);
-           /* Add further algorithm names here. */
-           if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
-               key->alg = &ssh_rsa;
-           else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
-               key->alg = &ssh_dss;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19))
-                key->alg = &ssh_ecdsa_nistp256;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19))
-                key->alg = &ssh_ecdsa_nistp384;
-            else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19))
-                key->alg = &ssh_ecdsa_nistp521;
-           else {
+            key->alg = find_pubkey_alg_len(alglen, alg);
+           if (!key->alg) {
                sfree(key);
                 fail_reason = "algorithm unknown";
                goto failure;
            }
 
            bloblen = msgend - p;
-           key->data = key->alg->openssh_createkey(&p, &bloblen);
+           key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen);
            if (!key->data) {
                sfree(key);
                 fail_reason = "key setup failed";
@@ -736,7 +711,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
            key->comment = comment;
 
             if (logfn) {
-                char *fingerprint = key->alg->fingerprint(key->data);
+                char *fingerprint = ssh2_fingerprint(key->alg, key->data);
                 plog(logctx, logfn, "submitted key: %s %s",
                      fingerprint, key->comment);
                 sfree(fingerprint);
@@ -830,7 +805,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
            p += b.len;
 
             if (logfn) {
-                char *fingerprint = fingerprint_ssh2_blob(b.blob, b.len);
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
                 plog(logctx, logfn, "unwanted key: %s", fingerprint);
                 sfree(fingerprint);
             }
@@ -928,6 +903,7 @@ void *pageant_failure_msg(int *outlen)
 
 void pageant_init(void)
 {
+    pageant_local = TRUE;
     rsakeys = newtree234(cmpkeys_rsa);
     ssh2keys = newtree234(cmpkeys_ssh2);
 }
@@ -954,12 +930,12 @@ int pageant_count_ssh2_keys(void)
 
 int pageant_add_ssh1_key(struct RSAKey *rkey)
 {
-    return add234(rsakeys, rkey) != rkey;
+    return add234(rsakeys, rkey) == rkey;
 }
 
 int pageant_add_ssh2_key(struct ssh2_userkey *skey)
 {
-    return add234(ssh2keys, skey) != skey;
+    return add234(ssh2keys, skey) == skey;
 }
 
 int pageant_delete_ssh1_key(struct RSAKey *rkey)
@@ -1125,6 +1101,7 @@ static int pageant_listen_accepting(Plug plug,
     struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
     struct pageant_conn_state *pc;
     const char *err;
+    char *peerinfo;
 
     pc = snew(struct pageant_conn_state);
     pc->fn = &connection_fn_table;
@@ -1141,14 +1118,18 @@ static int pageant_listen_accepting(Plug plug,
 
     sk_set_frozen(pc->connsock, 0);
 
-    /* FIXME: can we get any useful peer id info? */
-    plog(pl->logctx, pl->logfn, "%p: new connection", pc);
+    peerinfo = sk_peer_info(pc->connsock);
+    if (peerinfo) {
+        plog(pl->logctx, pl->logfn, "%p: new connection from %s",
+             pc, peerinfo);
+    } else {
+        plog(pl->logctx, pl->logfn, "%p: new connection", pc);
+    }
 
     return 0;
 }
 
-struct pageant_listen_state *pageant_listener_new(void *logctx,
-                                                  pageant_logfn_t logfn)
+struct pageant_listen_state *pageant_listener_new(void)
 {
     static const struct plug_function_table listener_fn_table = {
         NULL, /* no log function, because that's for outgoing connections */
@@ -1160,8 +1141,8 @@ struct pageant_listen_state *pageant_listener_new(void *logctx,
 
     struct pageant_listen_state *pl = snew(struct pageant_listen_state);
     pl->fn = &listener_fn_table;
-    pl->logctx = logctx;
-    pl->logfn = logfn;
+    pl->logctx = NULL;
+    pl->logfn = NULL;
     pl->listensock = NULL;
     return pl;
 }
@@ -1171,9 +1152,684 @@ void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock)
     pl->listensock = sock;
 }
 
+void pageant_listener_set_logfn(struct pageant_listen_state *pl,
+                                void *logctx, pageant_logfn_t logfn)
+{
+    pl->logctx = logctx;
+    pl->logfn = logfn;
+}
+
 void pageant_listener_free(struct pageant_listen_state *pl)
 {
     if (pl->listensock)
         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)
+{
+    if (!passphrases)                  /* in case we never set it up at all */
+        return;
+
+    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;
+
+       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+        agent_query_synchronous(request, 5, &vresponse, &resplen);
+       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;
+
+       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+       agent_query_synchronous(request, 5, &vresponse, &resplen);
+       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");
+                sfree(keylist);
+                sfree(blob);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           nkeys = toint(GET_32BIT(keylist));
+           if (nkeys < 0) {
+               *retstr = dupstr("Received broken key list from agent");
+                sfree(keylist);
+                sfree(blob);
+               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");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               } else {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = GET_32BIT(p);
+                    p += 4;
+                    keylistlen -= 4;
+
+                   if (n < 0 || n > keylistlen) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        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");
+                        sfree(keylist);
+                        sfree(blob);
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = GET_32BIT(p);
+                    p += 4;
+                    keylistlen -= 4;
+
+                   if (n < 0 || n > keylistlen) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        sfree(keylist);
+                        sfree(blob);
+                        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;
+                sfree(rkey);
+                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);
+            sfree(rkey);
+            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;
+
+           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);
+
+           agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                freersakey(rkey);
+                sfree(rkey);
+                sfree(request);
+                sfree(response);
+                return PAGEANT_ACTION_FAILURE;
+            }
+            freersakey(rkey);
+            sfree(rkey);
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh1_key(rkey)) {
+                freersakey(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;
+           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);
+
+           agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                sfree(request);
+                sfree(response);
+                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;
+}
+
+int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
+                      char **retstr)
+{
+    unsigned char *keylist, *p;
+    int i, nkeys, keylistlen;
+    char *comment;
+    struct pageant_pubkey cbkey;
+
+    keylist = pageant_get_keylist1(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        struct RSAKey rkey;
+        char fingerprint[128];
+        int n;
+
+        /* public blob and fingerprint */
+        memset(&rkey, 0, sizeof(rkey));
+        n = makekey(p, keylistlen, &rkey, NULL, 0);
+        if (n < 0 || n > keylistlen) {
+            freersakey(&rkey);
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        p += n, keylistlen -= n;
+        rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey);
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen);
+        cbkey.comment = comment;
+        cbkey.ssh_version = 1;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(cbkey.blob);
+        freersakey(&rkey);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    keylist = pageant_get_keylist2(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        char *fingerprint;
+        int n;
+
+        /* public blob */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        fingerprint = ssh2_fingerprint_blob(p, n);
+        cbkey.blob = p;
+        cbkey.bloblen = n;
+        p += n, keylistlen -= n;
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.ssh_version = 2;
+        cbkey.comment = comment;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(fingerprint);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    return PAGEANT_ACTION_OK;
+}
+
+int pageant_delete_key(struct pageant_pubkey *key, char **retstr)
+{
+    unsigned char *request, *response;
+    int reqlen, resplen, ret;
+    void *vresponse;
+
+    if (key->ssh_version == 1) {
+        reqlen = 5 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY;
+        memcpy(request + 5, key->blob, key->bloblen);
+    } else {
+        reqlen = 9 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH2_AGENTC_REMOVE_IDENTITY;
+        PUT_32BIT(request + 5, key->bloblen);
+        memcpy(request + 9, key->blob, key->bloblen);
+    }
+
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+        *retstr = dupstr("Agent failed to delete key");
+        ret = PAGEANT_ACTION_FAILURE;
+    } else {
+        *retstr = NULL;
+        ret = PAGEANT_ACTION_OK;
+    }
+    sfree(request);
+    sfree(response);
+    return ret;
+}
+
+int pageant_delete_all_keys(char **retstr)
+{
+    unsigned char request[5], *response;
+    int reqlen, resplen, success;
+    void *vresponse;
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES;
+    reqlen = 5;
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-2 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES;
+    reqlen = 5;
+    agent_query_synchronous(request, reqlen, &vresponse, &resplen);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-1 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    *retstr = NULL;
+    return PAGEANT_ACTION_OK;
+}
+
+struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key)
+{
+    struct pageant_pubkey *ret = snew(struct pageant_pubkey);
+    ret->blob = snewn(key->bloblen, unsigned char);
+    memcpy(ret->blob, key->blob, key->bloblen);
+    ret->bloblen = key->bloblen;
+    ret->comment = key->comment ? dupstr(key->comment) : NULL;
+    ret->ssh_version = key->ssh_version;
+    return ret;
+}
+
+void pageant_pubkey_free(struct pageant_pubkey *key)
+{
+    sfree(key->comment);
+    sfree(key->blob);
+    sfree(key);
+}