1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Pageant core: initial deferred decryption facility.

This adds an extension request to the agent protocol (named in our
private namespace, naturally) which allows you to upload a key file in
the form of a string containing an entire .ppk file. If the key is
encrypted, then Pageant stores it in such a way that it will show up
in the key list, and on the first attempt to sign something with it,
prompt for a passphrase (if it can), decrypt the key, and then answer
the request.

There are a lot of rough edges still to deal with, but this is good
enough to have successfully answered one request, so it's a start.
This commit is contained in:
Simon Tatham 2020-01-07 19:56:47 +00:00
parent 4d05eb424d
commit d8337e2070
2 changed files with 285 additions and 42 deletions

326
pageant.c
View File

@ -29,6 +29,10 @@ void random_read(void *buf, size_t size)
static bool pageant_local = false;
struct PageantClientDialogId {
int dummy;
};
typedef struct PageantKeySort PageantKeySort;
typedef struct PageantKey PageantKey;
typedef struct PageantAsyncOp PageantAsyncOp;
@ -99,7 +103,10 @@ struct PageantKey {
RSAKey *rkey; /* if ssh_version == 1 */
ssh2_userkey *skey; /* if ssh_version == 2 */
};
strbuf *encrypted_key_file;
bool decryption_prompt_active;
PageantKeyRequestNode blocked_requests;
PageantClientDialogId dlgid;
};
typedef struct PageantSignOp PageantSignOp;
@ -115,6 +122,7 @@ struct PageantSignOp {
static void failure(PageantClient *pc, PageantClientRequestId *reqid,
strbuf *sb, const char *fmt, ...);
static void fail_requests_for_key(PageantKey *pk, const char *reason);
static void pk_free(PageantKey *pk)
{
@ -129,19 +137,9 @@ static void pk_free(PageantKey *pk)
ssh_key_free(pk->skey->key);
sfree(pk->skey);
}
while (pk->blocked_requests.next != &pk->blocked_requests) {
PageantSignOp *so = container_of(pk->blocked_requests.next,
PageantSignOp, pkr);
so->pkr.next->prev = so->pkr.prev;
so->pkr.prev->next = so->pkr.next;
strbuf *sb = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, sb,
"key deleted from Pageant while signing request was pending");
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
ptrlen_from_strbuf(sb));
strbuf_free(sb);
pageant_async_op_unlink_and_free(&so->pao);
}
if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file);
fail_requests_for_key(pk, "key deleted from Pageant while signing "
"request was pending");
sfree(pk);
}
@ -331,27 +329,81 @@ static void signop_free(PageantAsyncOp *pao)
sfree(so);
}
static bool request_passphrase(PageantClient *pc, PageantKey *pk)
{
if (!pk->decryption_prompt_active) {
strbuf *sb = strbuf_new();
strbuf_catf(sb, "Enter passphrase to decrypt key '%s'", pk->comment);
bool created_dlg = pageant_client_ask_passphrase(
pc, &pk->dlgid, sb->s);
strbuf_free(sb);
if (!created_dlg)
return false;
pk->decryption_prompt_active = true;
}
return true;
}
static void signop_coroutine(PageantAsyncOp *pao)
{
PageantSignOp *so = container_of(pao, PageantSignOp, pao);
strbuf *response;
crBegin(so->crLine);
/*
* If we want to request a user interaction, we should set it up;
* arrange that when it finishes, it re-queues
* pageant_async_op_callback; and then crReturnV so that we resume
* from after that.
*/
if (0) crReturnV;
if (!so->pk->skey) {
assert(so->pk->encrypted_key_file);
if (!request_passphrase(so->pao.info->pc, so->pk)) {
response = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, response,
"on-demand decryption could not prompt for a "
"passphrase");
goto respond;
}
so->pkr.prev = so->pk->blocked_requests.prev;
so->pkr.next = &so->pk->blocked_requests;
so->pkr.prev->next = &so->pkr;
so->pkr.next->prev = &so->pkr;
crReturnV;
}
uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags;
if (so->flags & ~supported_flags) {
/*
* We MUST reject any message containing flags we don't
* understand.
*/
response = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, response,
"unsupported flag bits 0x%08"PRIx32,
so->flags & ~supported_flags);
goto respond;
}
char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags);
if (invalid) {
response = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, response,
"key invalid: %s", invalid);
sfree(invalid);
goto respond;
}
strbuf *signature = strbuf_new();
ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign),
so->flags, BinarySink_UPCAST(signature));
strbuf *response = strbuf_new();
response = strbuf_new();
put_byte(response, SSH2_AGENT_SIGN_RESPONSE);
put_stringsb(response, signature);
respond:
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
ptrlen_from_strbuf(response));
strbuf_free(response);
@ -365,6 +417,89 @@ static struct PageantAsyncOpVtable signop_vtable = {
signop_free,
};
static void fail_requests_for_key(PageantKey *pk, const char *reason)
{
while (pk->blocked_requests.next != &pk->blocked_requests) {
PageantSignOp *so = container_of(pk->blocked_requests.next,
PageantSignOp, pkr);
so->pkr.next->prev = so->pkr.prev;
so->pkr.prev->next = so->pkr.next;
strbuf *sb = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, sb, "%s", reason);
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
ptrlen_from_strbuf(sb));
strbuf_free(sb);
pageant_async_op_unlink_and_free(&so->pao);
}
}
static void unblock_requests_for_key(PageantKey *pk)
{
for (PageantKeyRequestNode *pkr = pk->blocked_requests.next;
pkr != &pk->blocked_requests; pkr = pkr->next) {
PageantSignOp *so = container_of(pk->blocked_requests.next,
PageantSignOp, pkr);
queue_toplevel_callback(pageant_async_op_callback, &so->pao);
}
}
void pageant_passphrase_request_success(PageantClientDialogId *dlgid,
ptrlen passphrase)
{
PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
if (!pk->skey) {
const char *error;
BinarySource src[1];
BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
pk->encrypted_key_file));
strbuf *ppsb = strbuf_new_nm();
put_datapl(ppsb, passphrase);
pk->skey = ppk_load_s(src, ppsb->s, &error);
strbuf_free(ppsb);
if (!pk->skey) {
fail_requests_for_key(pk, "unable to decrypt key");
return;
} else if (pk->skey == SSH2_WRONG_PASSPHRASE) {
/*
* Find a PageantClient to use for another attempt at
* request_passphrase.
*/
PageantKeyRequestNode *pkr = pk->blocked_requests.next;
if (pkr == &pk->blocked_requests) {
/*
* Special case: if all the requests have gone away at
* this point, we need not bother putting up a request
* at all any more.
*/
return;
}
PageantSignOp *so = container_of(pk->blocked_requests.next,
PageantSignOp, pkr);
if (!request_passphrase(so->pao.info->pc, pk)) {
fail_requests_for_key(pk, "unable to continue creating "
"passphrase prompts");
return;
}
}
}
unblock_requests_for_key(pk);
}
void pageant_passphrase_request_refused(PageantClientDialogId *dlgid)
{
PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
unblock_requests_for_key(pk);
}
typedef struct PageantImmOp PageantImmOp;
struct PageantImmOp {
int crLine;
@ -399,6 +534,8 @@ static struct PageantAsyncOpVtable immop_vtable = {
immop_free,
};
#define PUTTYEXT(base) PTRLEN_LITERAL(base "@putty.projects.tartarus.org")
void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
ptrlen msgpl)
{
@ -545,7 +682,7 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
{
PageantKey *pk;
ptrlen keyblob, sigdata;
uint32_t flags, supported_flags;
uint32_t flags;
pageant_client_log(pc, reqid, "request: SSH2_AGENTC_SIGN_REQUEST");
@ -587,24 +724,6 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
else
pageant_client_log(pc, reqid, "no signature flags");
supported_flags = ssh_key_alg(pk->skey->key)->supported_flags;
if (flags & ~supported_flags) {
/*
* We MUST reject any message containing flags we
* don't understand.
*/
failure(pc, reqid, sb, "unsupported flag bits 0x%08"PRIx32,
flags & ~supported_flags);
goto responded;
}
char *invalid = ssh_key_invalid(pk->skey->key, flags);
if (invalid) {
failure(pc, reqid, sb, "key invalid: %s", invalid);
sfree(invalid);
goto responded;
}
strbuf_free(sb); /* no immediate response */
PageantSignOp *so = snew(PageantSignOp);
@ -818,7 +937,7 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
}
pageant_client_log(pc, reqid,
"found with comment: %s", pk->skey->comment);
"found with comment: %s", pk->comment);
del234(keytree, pk);
keylist_update();
@ -860,6 +979,130 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
}
break;
case SSH2_AGENTC_EXTENSION:
{
ptrlen exttype = get_string(msg);
if (ptrlen_eq_ptrlen(exttype, PUTTYEXT("add-ppk"))) {
ptrlen keyfile = get_string(msg);
if (get_err(msg)) {
failure(pc, reqid, sb, "unable to decode request");
goto responded;
}
BinarySource src[1];
const char *error;
strbuf *public_blob = strbuf_new();
char *comment;
BinarySource_BARE_INIT_PL(src, keyfile);
if (!ppk_loadpub_s(src, NULL,
BinarySink_UPCAST(public_blob),
&comment, &error)) {
failure(pc, reqid, sb,
"add-ppk: failed to extract public key blob: %s",
error);
goto add_ppk_cleanup;
}
if (!pc->suppress_logging) {
char *fingerprint = ssh2_fingerprint_blob(
ptrlen_from_strbuf(public_blob));
pageant_client_log(pc, reqid, "add-ppk: %s %s",
fingerprint, comment);
sfree(fingerprint);
}
BinarySource_BARE_INIT_PL(src, keyfile);
bool encrypted = ppk_encrypted_s(src, NULL);
if (!encrypted) {
/* If the key isn't encrypted, then we should just
* load and add it in the obvious way. */
BinarySource_BARE_INIT_PL(src, keyfile);
ssh2_userkey *skey = ppk_load_s(src, NULL, &error);
if (!skey) {
failure(pc, reqid, sb,
"add-ppk: failed to load private key: %s",
error);
} else if (pageant_add_ssh2_key(skey)) {
keylist_update();
put_byte(sb, SSH_AGENT_SUCCESS);
pageant_client_log(pc, reqid,
"reply: SSH_AGENT_SUCCESS"
" (loaded unencrypted PPK)");
} else {
failure(pc, reqid, sb, "key already present");
if (skey->key)
ssh_key_free(skey->key);
if (skey->comment)
sfree(skey->comment);
sfree(skey);
}
goto add_ppk_cleanup;
}
PageantKeySort sort =
keysort(2, ptrlen_from_strbuf(public_blob));
PageantKey *pk = find234(keytree, &sort, NULL);
if (pk) {
/*
* This public key blob already exists in the
* keytree. Add the encrypted key file to the
* existing record, if it doesn't have one already.
*/
if (!pk->encrypted_key_file) {
pk->encrypted_key_file = strbuf_new_nm();
put_datapl(pk->encrypted_key_file, keyfile);
put_byte(sb, SSH_AGENT_SUCCESS);
pageant_client_log(pc, reqid,
"reply: SSH_AGENT_SUCCESS (added"
" encrypted PPK to existing key"
" record)");
} else {
failure(pc, reqid, sb, "key already present");
}
} else {
/*
* We're adding a new key record containing only
* an encrypted key file.
*/
PageantKey *pk = snew(PageantKey);
memset(pk, 0, sizeof(PageantKey));
pk->blocked_requests.next = pk->blocked_requests.prev =
&pk->blocked_requests;
pk->sort.ssh_version = 2;
pk->public_blob = public_blob;
public_blob = NULL;
pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob);
pk->comment = dupstr(comment);
pk->encrypted_key_file = strbuf_new_nm();
put_datapl(pk->encrypted_key_file, keyfile);
PageantKey *added = add234(keytree, pk);
assert(added == pk); (void)added;
put_byte(sb, SSH_AGENT_SUCCESS);
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"
" (made new encrypted-only key"
" record)");
}
add_ppk_cleanup:
if (public_blob)
strbuf_free(public_blob);
sfree(comment);
goto responded;
} else {
failure(pc, reqid, sb, "key not found");
goto responded;
}
}
break;
default:
pageant_client_log(pc, reqid, "request: unknown message type %d",
type);
@ -1045,7 +1288,6 @@ static bool pageant_conn_ask_passphrase(
return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, msg);
}
static const struct PageantClientVtable pageant_connection_clientvt = {
pageant_conn_log,
pageant_conn_got_response,

1
ssh.h
View File

@ -1497,6 +1497,7 @@ enum {
#define SSH2_AGENTC_ADD_IDENTITY 17
#define SSH2_AGENTC_REMOVE_IDENTITY 18
#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
#define SSH2_AGENTC_EXTENSION 27
/*
* Assorted other SSH-related enumerations.