]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Unix Pageant: support -d, to delete a key from the agent.
authorSimon Tatham <anakin@pobox.com>
Tue, 12 May 2015 12:27:33 +0000 (13:27 +0100)
committerSimon Tatham <anakin@pobox.com>
Tue, 12 May 2015 13:56:25 +0000 (14:56 +0100)
Unlike ssh-add, we can identify the key by its comment or by a prefix
of its fingerprint as well as using a public key file on disk. The
string given as an argument to -d is interpreted as whichever of those
things matches; disambiguating prefixes are available if needed.

pageant.c
pageant.h
unix/uxpgnt.c

index 8f0747cbe20b7aa44fc9241c22a29850b34cc6e2..754af9c6aca103c2b649446f72a92cb1da706aee 100644 (file)
--- a/pageant.c
+++ b/pageant.c
@@ -1589,6 +1589,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
     unsigned char *keylist, *p;
     int i, nkeys, keylistlen;
     char *comment;
+    struct pageant_pubkey cbkey;
 
     keylist = pageant_get_keylist1(&keylistlen);
     if (keylistlen < 4) {
@@ -1640,7 +1641,10 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
         comment = dupprintf("%.*s", (int)n, (const char *)p);
         p += n, keylistlen -= n;
 
-        callback(callback_ctx, fingerprint, comment);
+        cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen);
+        cbkey.ssh_version = 1;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(cbkey.blob);
         freersakey(&rkey);
         sfree(comment);
     }
@@ -1685,6 +1689,8 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
             return PAGEANT_ACTION_FAILURE;
         }
         fingerprint = fingerprint_ssh2_blob(p, n);
+        cbkey.blob = p;
+        cbkey.bloblen = n;
         p += n, keylistlen -= n;
 
         /* comment */
@@ -1705,7 +1711,8 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
         comment = dupprintf("%.*s", (int)n, (const char *)p);
         p += n, keylistlen -= n;
 
-        callback(callback_ctx, fingerprint, comment);
+        cbkey.ssh_version = 2;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
         sfree(fingerprint);
         sfree(comment);
     }
@@ -1719,3 +1726,55 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
 
     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);
+    }
+
+    ret = agent_query(request, reqlen, &vresponse, &resplen, NULL, NULL);
+    assert(ret == 1);
+    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;
+}
+
+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->ssh_version = key->ssh_version;
+    return ret;
+}
+
+void pageant_pubkey_free(struct pageant_pubkey *key)
+{
+    sfree(key->blob);
+    sfree(key);
+}
index ec94a50c9961b81603ddc141e3f590a941acdd50..4a26ad93b10863e166a7eed6a0cf43c059c1bf9a 100644 (file)
--- a/pageant.h
+++ b/pageant.h
@@ -120,8 +120,22 @@ enum {
 int pageant_add_keyfile(Filename *filename, const char *passphrase,
                         char **retstr);
 void pageant_forget_passphrases(void);
+
+struct pageant_pubkey {
+    /* Everything needed to identify a public key found by
+     * pageant_enum_keys and pass it back to the agent or other code
+     * later */
+    void *blob;
+    int bloblen;
+    int ssh_version;
+};
+struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key);
+void pageant_pubkey_free(struct pageant_pubkey *key);
+
 typedef void (*pageant_key_enum_fn_t)(void *ctx,
                                       const char *fingerprint,
-                                      const char *comment);
+                                      const char *comment,
+                                      struct pageant_pubkey *key);
 int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
                       char **retstr);
+int pageant_delete_key(struct pageant_pubkey *key, char **retstr);
index 4d2aa08eb1a5ba587d23c215240d887d5edc16bf..6e5923c2b5068f7aea81402e2950117a8312e285 100644 (file)
@@ -7,6 +7,7 @@
 #include <errno.h>
 #include <assert.h>
 #include <signal.h>
+#include <ctype.h>
 
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -354,14 +355,181 @@ static int unix_add_keyfile(const char *filename_str)
     return ret;
 }
 
-void key_list_callback(void *ctx, const char *fingerprint, const char *comment)
+void key_list_callback(void *ctx, const char *fingerprint,
+                       const char *comment, struct pageant_pubkey *key)
 {
     printf("%s %s\n", fingerprint, comment);
 }
 
+struct key_find_ctx {
+    const char *string;
+    int match_fp, match_comment;
+    struct pageant_pubkey *found;
+    int nfound;
+};
+
+int match_fingerprint_string(const char *string, const char *fingerprint)
+{
+    const char *hash;
+
+    /* Find the hash in the fingerprint string. It'll be the word at the end. */
+    hash = strrchr(fingerprint, ' ');
+    assert(hash);
+    hash++;
+
+    /* Now see if the search string is a prefix of the full hash,
+     * neglecting colons and case differences. */
+    while (1) {
+        while (*string == ':') string++;
+        while (*hash == ':') hash++;
+        if (!*string)
+            return TRUE;
+        if (tolower((unsigned char)*string) != tolower((unsigned char)*hash))
+            return FALSE;
+        string++;
+        hash++;
+    }
+}
+
+void key_find_callback(void *vctx, const char *fingerprint,
+                       const char *comment, struct pageant_pubkey *key)
+{
+    struct key_find_ctx *ctx = (struct key_find_ctx *)vctx;
+
+    if ((ctx->match_comment && !strcmp(ctx->string, comment)) ||
+        (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprint)))
+    {
+        if (!ctx->found)
+            ctx->found = pageant_pubkey_copy(key);
+        ctx->nfound++;
+    }
+}
+
+struct pageant_pubkey *find_key(const char *string, char **retstr)
+{
+    struct key_find_ctx actx, *ctx = &actx;
+    struct pageant_pubkey key_in, *key_ret;
+    int try_file = TRUE, try_fp = TRUE, try_comment = TRUE;
+    int file_errors = FALSE;
+
+    /*
+     * Trim off disambiguating prefixes telling us how to interpret
+     * the provided string.
+     */
+    if (!strncmp(string, "file:", 5)) {
+        string += 5;
+        try_fp = try_comment = FALSE;
+        file_errors = TRUE; /* also report failure to load the file */
+    } else if (!strncmp(string, "comment:", 8)) {
+        string += 8;
+        try_file = try_fp = FALSE;
+    } else if (!strncmp(string, "fp:", 3)) {
+        string += 3;
+        try_file = try_comment = FALSE;
+    } else if (!strncmp(string, "fingerprint:", 12)) {
+        string += 12;
+        try_file = try_comment = FALSE;
+    }
+
+    /*
+     * Try interpreting the string as a key file name.
+     */
+    if (try_file) {
+        Filename *fn = filename_from_str(string);
+        int keytype = key_type(fn);
+        if (keytype == SSH_KEYTYPE_SSH1 ||
+            keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+            const char *error;
+
+            if (!rsakey_pubblob(fn, &key_in.blob, &key_in.bloblen,
+                                NULL, &error)) {
+                if (file_errors) {
+                    *retstr = dupprintf("unable to load file '%s': %s",
+                                        string, error);
+                    filename_free(fn);
+                    return NULL;
+                }
+            }
+
+            /*
+             * If we've successfully loaded the file, stop here - we
+             * already have a key blob and need not go to the agent to
+             * list things.
+             */
+            key_in.ssh_version = 1;
+            key_ret = pageant_pubkey_copy(&key_in);
+            sfree(key_in.blob);
+            filename_free(fn);
+            return key_ret;
+        } else if (keytype == SSH_KEYTYPE_SSH2 ||
+                   keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+                   keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+            const char *error;
+
+            if ((key_in.blob = ssh2_userkey_loadpub(fn, NULL,
+                                                    &key_in.bloblen,
+                                                    NULL, &error)) == NULL) {
+                if (file_errors) {
+                    *retstr = dupprintf("unable to load file '%s': %s",
+                                        string, error);
+                    filename_free(fn);
+                    return NULL;
+                }
+            }
+
+            /*
+             * If we've successfully loaded the file, stop here - we
+             * already have a key blob and need not go to the agent to
+             * list things.
+             */
+            key_in.ssh_version = 2;
+            key_ret = pageant_pubkey_copy(&key_in);
+            sfree(key_in.blob);
+            filename_free(fn);
+            return key_ret;
+        } else {
+            if (file_errors) {
+                *retstr = dupprintf("unable to load key file '%s': %s",
+                                    string, key_type_to_str(keytype));
+                filename_free(fn);
+                return NULL;
+            }
+        }
+        filename_free(fn);
+    }
+
+    /*
+     * Failing that, go through the keys in the agent, and match
+     * against fingerprints and comments as appropriate.
+     */
+    ctx->string = string;
+    ctx->match_fp = try_fp;
+    ctx->match_comment = try_comment;
+    ctx->found = NULL;
+    ctx->nfound = 0;
+    if (pageant_enum_keys(key_find_callback, ctx, retstr) ==
+        PAGEANT_ACTION_FAILURE)
+        return NULL;
+
+    if (ctx->nfound == 0) {
+        *retstr = dupstr("no key matched");
+        assert(!ctx->found);
+        return NULL;
+    } else if (ctx->nfound > 1) {
+        *retstr = dupstr("multiple keys matched");
+        assert(ctx->found);
+        pageant_pubkey_free(ctx->found);
+        return NULL;
+    }
+
+    assert(ctx->found);
+    return ctx->found;
+}
+
 void run_client(void)
 {
     const struct cmdline_key_action *act;
+    struct pageant_pubkey *key;
     int errors = FALSE;
     char *retstr;
 
@@ -385,6 +553,17 @@ void run_client(void)
             }
             break;
           case KEYACT_CLIENT_DEL:
+            key = NULL;
+            if (!(key = find_key(act->filename, &retstr)) ||
+                pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
+                fprintf(stderr, "pageant: deleting key '%s': %s\n",
+                        act->filename, retstr);
+                sfree(retstr);
+                errors = TRUE;
+            }
+            if (key)
+                pageant_pubkey_free(key);
+            break;
           case KEYACT_CLIENT_DEL_ALL:
           case KEYACT_CLIENT_LIST_FULL:
             fprintf(stderr, "NYI\n");