mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-03-22 14:39:24 -05:00
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.
This commit is contained in:
parent
4204a53f6d
commit
4d88fe3dde
63
pageant.c
63
pageant.c
@ -1589,6 +1589,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
|||||||
unsigned char *keylist, *p;
|
unsigned char *keylist, *p;
|
||||||
int i, nkeys, keylistlen;
|
int i, nkeys, keylistlen;
|
||||||
char *comment;
|
char *comment;
|
||||||
|
struct pageant_pubkey cbkey;
|
||||||
|
|
||||||
keylist = pageant_get_keylist1(&keylistlen);
|
keylist = pageant_get_keylist1(&keylistlen);
|
||||||
if (keylistlen < 4) {
|
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);
|
comment = dupprintf("%.*s", (int)n, (const char *)p);
|
||||||
p += n, keylistlen -= n;
|
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);
|
freersakey(&rkey);
|
||||||
sfree(comment);
|
sfree(comment);
|
||||||
}
|
}
|
||||||
@ -1685,6 +1689,8 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
|||||||
return PAGEANT_ACTION_FAILURE;
|
return PAGEANT_ACTION_FAILURE;
|
||||||
}
|
}
|
||||||
fingerprint = fingerprint_ssh2_blob(p, n);
|
fingerprint = fingerprint_ssh2_blob(p, n);
|
||||||
|
cbkey.blob = p;
|
||||||
|
cbkey.bloblen = n;
|
||||||
p += n, keylistlen -= n;
|
p += n, keylistlen -= n;
|
||||||
|
|
||||||
/* comment */
|
/* 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);
|
comment = dupprintf("%.*s", (int)n, (const char *)p);
|
||||||
p += n, keylistlen -= n;
|
p += n, keylistlen -= n;
|
||||||
|
|
||||||
callback(callback_ctx, fingerprint, comment);
|
cbkey.ssh_version = 2;
|
||||||
|
callback(callback_ctx, fingerprint, comment, &cbkey);
|
||||||
sfree(fingerprint);
|
sfree(fingerprint);
|
||||||
sfree(comment);
|
sfree(comment);
|
||||||
}
|
}
|
||||||
@ -1719,3 +1726,55 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
|||||||
|
|
||||||
return PAGEANT_ACTION_OK;
|
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);
|
||||||
|
}
|
||||||
|
16
pageant.h
16
pageant.h
@ -120,8 +120,22 @@ enum {
|
|||||||
int pageant_add_keyfile(Filename *filename, const char *passphrase,
|
int pageant_add_keyfile(Filename *filename, const char *passphrase,
|
||||||
char **retstr);
|
char **retstr);
|
||||||
void pageant_forget_passphrases(void);
|
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,
|
typedef void (*pageant_key_enum_fn_t)(void *ctx,
|
||||||
const char *fingerprint,
|
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,
|
int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
||||||
char **retstr);
|
char **retstr);
|
||||||
|
int pageant_delete_key(struct pageant_pubkey *key, char **retstr);
|
||||||
|
181
unix/uxpgnt.c
181
unix/uxpgnt.c
@ -7,6 +7,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
@ -354,14 +355,181 @@ static int unix_add_keyfile(const char *filename_str)
|
|||||||
return ret;
|
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);
|
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)
|
void run_client(void)
|
||||||
{
|
{
|
||||||
const struct cmdline_key_action *act;
|
const struct cmdline_key_action *act;
|
||||||
|
struct pageant_pubkey *key;
|
||||||
int errors = FALSE;
|
int errors = FALSE;
|
||||||
char *retstr;
|
char *retstr;
|
||||||
|
|
||||||
@ -385,6 +553,17 @@ void run_client(void)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEYACT_CLIENT_DEL:
|
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_DEL_ALL:
|
||||||
case KEYACT_CLIENT_LIST_FULL:
|
case KEYACT_CLIENT_LIST_FULL:
|
||||||
fprintf(stderr, "NYI\n");
|
fprintf(stderr, "NYI\n");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user