From: Simon Tatham Date: Tue, 12 May 2015 12:27:33 +0000 (+0100) Subject: Unix Pageant: support -d, to delete a key from the agent. X-Git-Tag: 0.68~564 X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=commitdiff_plain;h=4d88fe3dde3448f3e940c424cf1de3221c3fde10;p=PuTTY.git Unix Pageant: support -d, to delete a key from the agent. 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. --- diff --git a/pageant.c b/pageant.c index 8f0747cb..754af9c6 100644 --- 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); +} diff --git a/pageant.h b/pageant.h index ec94a50c..4a26ad93 100644 --- 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); diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c index 4d2aa08e..6e5923c2 100644 --- a/unix/uxpgnt.c +++ b/unix/uxpgnt.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -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");