/* * pageant.c: cross-platform code to implement Pageant. */ #include #include #include #include "putty.h" #include "mpint.h" #include "ssh.h" #include "sshcr.h" #include "pageant.h" /* * We need this to link with the RSA code, because rsa_ssh1_encrypt() * pads its data with random bytes. Since we only use rsa_ssh1_decrypt() * and the signing functions, which are deterministic, this should * never be called. * * If it _is_ called, there is a _serious_ problem, because it * won't generate true random numbers. So we must scream, panic, * and exit immediately if that should happen. */ void random_read(void *buf, size_t size) { modalfatalbox("Internal error: attempt to use random numbers in Pageant"); } static bool pageant_local = false; struct PageantClientDialogId { int dummy; }; typedef struct PageantKeySort PageantKeySort; typedef struct PageantKey PageantKey; typedef struct PageantAsyncOp PageantAsyncOp; typedef struct PageantAsyncOpVtable PageantAsyncOpVtable; typedef struct PageantClientRequestNode PageantClientRequestNode; typedef struct PageantKeyRequestNode PageantKeyRequestNode; struct PageantClientRequestNode { PageantClientRequestNode *prev, *next; }; struct PageantKeyRequestNode { PageantKeyRequestNode *prev, *next; }; struct PageantClientInfo { PageantClient *pc; /* goes to NULL when client is unregistered */ PageantClientRequestNode head; }; struct PageantAsyncOp { const PageantAsyncOpVtable *vt; PageantClientInfo *info; PageantClientRequestNode cr; PageantClientRequestId *reqid; }; struct PageantAsyncOpVtable { void (*coroutine)(PageantAsyncOp *pao); void (*free)(PageantAsyncOp *pao); }; static inline void pageant_async_op_coroutine(PageantAsyncOp *pao) { pao->vt->coroutine(pao); } static inline void pageant_async_op_free(PageantAsyncOp *pao) { delete_callbacks_for_context(pao); pao->vt->free(pao); } static inline void pageant_async_op_unlink(PageantAsyncOp *pao) { pao->cr.prev->next = pao->cr.next; pao->cr.next->prev = pao->cr.prev; } static inline void pageant_async_op_unlink_and_free(PageantAsyncOp *pao) { pageant_async_op_unlink(pao); pageant_async_op_free(pao); } static void pageant_async_op_callback(void *vctx) { pageant_async_op_coroutine((PageantAsyncOp *)vctx); } /* * Master list of all the keys we have stored, in any form at all. */ static tree234 *keytree; struct PageantKeySort { /* Prefix of the main PageantKey structure which contains all the * data that the sorting order depends on. Also simple enough that * you can construct one for lookup purposes. */ int ssh_version; /* 1 or 2; primary sort key */ ptrlen public_blob; /* secondary sort key */ }; struct PageantKey { PageantKeySort sort; strbuf *public_blob; /* the true owner of sort.public_blob */ char *comment; /* stored separately, whether or not in rkey/skey */ union { 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; struct PageantSignOp { PageantKey *pk; strbuf *data_to_sign; unsigned flags; int crLine; unsigned char failure_type; PageantKeyRequestNode pkr; PageantAsyncOp pao; }; /* Master lock that indicates whether a GUI request is currently in * progress */ static bool gui_request_in_progress = false; static void failure(PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...); static void fail_requests_for_key(PageantKey *pk, const char *reason); static void pk_free(PageantKey *pk) { if (pk->public_blob) strbuf_free(pk->public_blob); sfree(pk->comment); if (pk->sort.ssh_version == 1 && pk->rkey) { freersakey(pk->rkey); sfree(pk->rkey); } if (pk->sort.ssh_version == 2 && pk->skey) { sfree(pk->skey->comment); ssh_key_free(pk->skey->key); sfree(pk->skey); } 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); } static int cmpkeys(void *av, void *bv) { PageantKeySort *a = (PageantKeySort *)av, *b = (PageantKeySort *)bv; if (a->ssh_version != b->ssh_version) return a->ssh_version < b->ssh_version ? -1 : +1; else return ptrlen_strcmp(a->public_blob, b->public_blob); } static inline PageantKeySort keysort(int version, ptrlen blob) { PageantKeySort sort; sort.ssh_version = version; sort.public_blob = blob; return sort; } static strbuf *makeblob1(RSAKey *rkey) { strbuf *blob = strbuf_new(); rsa_ssh1_public_blob(BinarySink_UPCAST(blob), rkey, RSA_SSH1_EXPONENT_FIRST); return blob; } static strbuf *makeblob2(ssh2_userkey *skey) { strbuf *blob = strbuf_new(); ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob)); return blob; } static PageantKey *findkey1(RSAKey *reqkey) { strbuf *blob = makeblob1(reqkey); PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); PageantKey *toret = find234(keytree, &sort, NULL); strbuf_free(blob); return toret; } static PageantKey *findkey2(ptrlen blob) { PageantKeySort sort = keysort(2, blob); return find234(keytree, &sort, NULL); } static int find_first_key_for_version(int ssh_version) { PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL("")); int pos; if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos)) return pos; return count234(keytree); } static int count_keys(int ssh_version) { return (find_first_key_for_version(ssh_version + 1) - find_first_key_for_version(ssh_version)); } int pageant_count_ssh1_keys(void) { return count_keys(1); } int pageant_count_ssh2_keys(void) { return count_keys(2); } bool pageant_add_ssh1_key(RSAKey *rkey) { PageantKey *pk = snew(PageantKey); memset(pk, 0, sizeof(PageantKey)); pk->sort.ssh_version = 1; pk->public_blob = makeblob1(rkey); pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); pk->blocked_requests.next = pk->blocked_requests.prev = &pk->blocked_requests; if (add234(keytree, pk) == pk) { pk->rkey = rkey; if (rkey->comment) pk->comment = dupstr(rkey->comment); return true; } else { pk_free(pk); return false; } } bool pageant_add_ssh2_key(ssh2_userkey *skey) { PageantKey *pk = snew(PageantKey); memset(pk, 0, sizeof(PageantKey)); pk->sort.ssh_version = 2; pk->public_blob = makeblob2(skey); pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); pk->blocked_requests.next = pk->blocked_requests.prev = &pk->blocked_requests; if (add234(keytree, pk) == pk) { pk->skey = skey; if (skey->comment) pk->comment = dupstr(skey->comment); return true; } else { pk_free(pk); return false; } } static void remove_all_keys(int ssh_version) { int start = find_first_key_for_version(ssh_version); int end = find_first_key_for_version(ssh_version + 1); while (end > start) { PageantKey *pk = delpos234(keytree, --end); assert(pk->sort.ssh_version == ssh_version); pk_free(pk); } } static void list_keys(BinarySink *bs, int ssh_version) { int i; PageantKey *pk; put_uint32(bs, count_keys(ssh_version)); for (i = find_first_key_for_version(ssh_version); NULL != (pk = index234(keytree, i)); i++) { if (pk->sort.ssh_version != ssh_version) break; if (ssh_version > 1) put_stringpl(bs, pk->sort.public_blob); else put_datapl(bs, pk->sort.public_blob); /* no header */ put_stringpl(bs, ptrlen_from_asciz(pk->comment)); } } void pageant_make_keylist1(BinarySink *bs) { list_keys(bs, 1); } void pageant_make_keylist2(BinarySink *bs) { list_keys(bs, 2); } void pageant_register_client(PageantClient *pc) { pc->info = snew(PageantClientInfo); pc->info->pc = pc; pc->info->head.prev = pc->info->head.next = &pc->info->head; } void pageant_unregister_client(PageantClient *pc) { PageantClientInfo *info = pc->info; assert(info); assert(info->pc == pc); while (pc->info->head.next != &pc->info->head) { PageantAsyncOp *pao = container_of(pc->info->head.next, PageantAsyncOp, cr); pageant_async_op_unlink_and_free(pao); } sfree(pc->info); } static PRINTF_LIKE(5, 6) void failure( PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...) { strbuf_clear(sb); put_byte(sb, type); if (!pc->suppress_logging) { va_list ap; va_start(ap, fmt); char *msg = dupvprintf(fmt, ap); va_end(ap); pageant_client_log(pc, reqid, "reply: SSH_AGENT_FAILURE (%s)", msg); sfree(msg); } } static void signop_link(PageantSignOp *so) { assert(!so->pkr.prev); assert(!so->pkr.next); 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; } static void signop_unlink(PageantSignOp *so) { if (so->pkr.next) { assert(so->pkr.prev); so->pkr.next->prev = so->pkr.prev; so->pkr.prev->next = so->pkr.next; } else { assert(!so->pkr.prev); } } static void signop_free(PageantAsyncOp *pao) { PageantSignOp *so = container_of(pao, PageantSignOp, pao); strbuf_free(so->data_to_sign); sfree(so); } static bool request_passphrase(PageantClient *pc, PageantKey *pk) { if (!pk->decryption_prompt_active) { assert(!gui_request_in_progress); 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; gui_request_in_progress = true; 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); while (!so->pk->skey && gui_request_in_progress) 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, so->failure_type, "on-demand decryption could not " "prompt for a passphrase"); goto respond; } signop_link(so); crReturnV; signop_unlink(so); } 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, so->failure_type, "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, so->failure_type, "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)); 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); pageant_async_op_unlink_and_free(&so->pao); crFinishFreedV; } static const PageantAsyncOpVtable signop_vtable = { .coroutine = signop_coroutine, .free = 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); signop_unlink(so); strbuf *sb = strbuf_new(); failure(so->pao.info->pc, so->pao.reqid, sb, so->failure_type, "%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); assert(gui_request_in_progress); gui_request_in_progress = false; pk->decryption_prompt_active = false; 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) { pk->skey = NULL; /* * 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); pk->decryption_prompt_active = false; 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); assert(gui_request_in_progress); gui_request_in_progress = false; pk->decryption_prompt_active = false; fail_requests_for_key(pk, "user refused to supply passphrase"); } typedef struct PageantImmOp PageantImmOp; struct PageantImmOp { int crLine; strbuf *response; PageantAsyncOp pao; }; static void immop_free(PageantAsyncOp *pao) { PageantImmOp *io = container_of(pao, PageantImmOp, pao); if (io->response) strbuf_free(io->response); sfree(io); } static void immop_coroutine(PageantAsyncOp *pao) { PageantImmOp *io = container_of(pao, PageantImmOp, pao); crBegin(io->crLine); if (0) crReturnV; pageant_client_got_response(io->pao.info->pc, io->pao.reqid, ptrlen_from_strbuf(io->response)); pageant_async_op_unlink_and_free(&io->pao); crFinishFreedV; } static const PageantAsyncOpVtable immop_vtable = { .coroutine = immop_coroutine, .free = immop_free, }; static bool reencrypt_key(PageantKey *pk) { if (pk->sort.ssh_version != 2) { /* * We don't support storing SSH-1 keys in encrypted form at * all. */ return false; } if (!pk->encrypted_key_file) { /* * We can't re-encrypt a key if it doesn't have an encrypted * form. (We could make one up, of course - but with what * passphrase that we could expect the user to know later?) */ return false; } /* Only actually free pk->skey if it exists. But we return success * regardless, so that 'please ensure this key isn't stored * decrypted' is idempotent. */ if (pk->skey) { sfree(pk->skey->comment); ssh_key_free(pk->skey->key); sfree(pk->skey); pk->skey = NULL; } return true; } #define PUTTYEXT(base) base "@putty.projects.tartarus.org" #define KNOWN_EXTENSIONS(X) \ X(EXT_QUERY, "query") \ X(EXT_ADD_PPK, PUTTYEXT("add-ppk")) \ X(EXT_REENCRYPT, PUTTYEXT("reencrypt")) \ X(EXT_REENCRYPT_ALL, PUTTYEXT("reencrypt-all")) \ /* end of list */ #define DECL_EXT_ENUM(id, name) id, enum Extension { KNOWN_EXTENSIONS(DECL_EXT_ENUM) EXT_UNKNOWN }; #define DEF_EXT_NAMES(id, name) PTRLEN_DECL_LITERAL(name), static const ptrlen extension_names[] = { KNOWN_EXTENSIONS(DEF_EXT_NAMES) }; static PageantAsyncOp *pageant_make_op( PageantClient *pc, PageantClientRequestId *reqid, ptrlen msgpl) { BinarySource msg[1]; strbuf *sb = strbuf_new_nm(); unsigned char failure_type = SSH_AGENT_FAILURE; int type; #define fail(...) failure(pc, reqid, sb, failure_type, __VA_ARGS__) BinarySource_BARE_INIT_PL(msg, msgpl); type = get_byte(msg); if (get_err(msg)) { fail("message contained no type code"); goto responded; } switch (type) { case SSH1_AGENTC_REQUEST_RSA_IDENTITIES: { /* * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER. */ pageant_client_log(pc, reqid, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES"); put_byte(sb, SSH1_AGENT_RSA_IDENTITIES_ANSWER); pageant_make_keylist1(BinarySink_UPCAST(sb)); pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; RSAKey *rkey; for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) { char *fingerprint = rsa_ssh1_fingerprint(rkey); pageant_client_log(pc, reqid, "returned key: %s", fingerprint); sfree(fingerprint); } } break; } case SSH2_AGENTC_REQUEST_IDENTITIES: { /* * Reply with SSH2_AGENT_IDENTITIES_ANSWER. */ pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REQUEST_IDENTITIES"); put_byte(sb, SSH2_AGENT_IDENTITIES_ANSWER); pageant_make_keylist2(BinarySink_UPCAST(sb)); pageant_client_log(pc, reqid, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; ssh2_userkey *skey; for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) { char *fingerprint = ssh2_fingerprint(skey->key); pageant_client_log(pc, reqid, "returned key: %s %s", fingerprint, skey->comment); sfree(fingerprint); } } break; } case SSH1_AGENTC_RSA_CHALLENGE: { /* * Reply with either SSH1_AGENT_RSA_RESPONSE or * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ RSAKey reqkey; PageantKey *pk; mp_int *challenge, *response; ptrlen session_id; unsigned response_type; unsigned char response_md5[16]; int i; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_RSA_CHALLENGE"); response = NULL; memset(&reqkey, 0, sizeof(reqkey)); get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); challenge = get_mp_ssh1(msg); session_id = get_data(msg, 16); response_type = get_uint32(msg); if (get_err(msg)) { fail("unable to decode request"); goto challenge1_cleanup; } if (response_type != 1) { fail("response type other than 1 not supported"); goto challenge1_cleanup; } if (!pc->suppress_logging) { char *fingerprint; reqkey.comment = NULL; fingerprint = rsa_ssh1_fingerprint(&reqkey); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } if ((pk = findkey1(&reqkey)) == NULL) { fail("key not found"); goto challenge1_cleanup; } response = rsa_ssh1_decrypt(challenge, pk->rkey); { ssh_hash *h = ssh_hash_new(&ssh_md5); for (i = 0; i < 32; i++) put_byte(h, mp_get_byte(response, 31 - i)); put_datapl(h, session_id); ssh_hash_final(h, response_md5); } put_byte(sb, SSH1_AGENT_RSA_RESPONSE); put_data(sb, response_md5, 16); pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE"); challenge1_cleanup: if (response) mp_free(response); mp_free(challenge); freersakey(&reqkey); break; } case SSH2_AGENTC_SIGN_REQUEST: { /* * Reply with either SSH2_AGENT_SIGN_RESPONSE or * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ PageantKey *pk; ptrlen keyblob, sigdata; uint32_t flags; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_SIGN_REQUEST"); keyblob = get_string(msg); sigdata = get_string(msg); if (get_err(msg)) { fail("unable to decode request"); goto responded; } /* * Later versions of the agent protocol added a flags word * on the end of the sign request. That hasn't always been * there, so we don't complain if we don't find it. * * get_uint32 will default to returning zero if no data is * available. */ bool have_flags = false; flags = get_uint32(msg); if (!get_err(msg)) have_flags = true; if (!pc->suppress_logging) { char *fingerprint = ssh2_fingerprint_blob(keyblob); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } if ((pk = findkey2(keyblob)) == NULL) { fail("key not found"); goto responded; } if (have_flags) pageant_client_log(pc, reqid, "signature flags = 0x%08"PRIx32, flags); else pageant_client_log(pc, reqid, "no signature flags"); strbuf_free(sb); /* no immediate response */ PageantSignOp *so = snew(PageantSignOp); so->pao.vt = &signop_vtable; so->pao.info = pc->info; so->pao.cr.prev = pc->info->head.prev; so->pao.cr.next = &pc->info->head; so->pao.reqid = reqid; so->pk = pk; so->pkr.prev = so->pkr.next = NULL; so->data_to_sign = strbuf_new(); put_datapl(so->data_to_sign, sigdata); so->flags = flags; so->failure_type = failure_type; so->crLine = 0; return &so->pao; break; } case SSH1_AGENTC_ADD_RSA_IDENTITY: { /* * Add to the list and return SSH_AGENT_SUCCESS, or * SSH_AGENT_FAILURE if the key was malformed. */ RSAKey *key; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_ADD_RSA_IDENTITY"); key = get_rsa_ssh1_priv_agent(msg); key->comment = mkstr(get_string(msg)); if (get_err(msg)) { fail("unable to decode request"); goto add1_cleanup; } if (!rsa_verify(key)) { fail("key is invalid"); goto add1_cleanup; } if (!pc->suppress_logging) { char *fingerprint = rsa_ssh1_fingerprint(key); pageant_client_log(pc, reqid, "submitted key: %s", fingerprint); sfree(fingerprint); } if (pageant_add_ssh1_key(key)) { keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); key = NULL; /* don't free it in cleanup */ } else { fail("key already present"); } add1_cleanup: if (key) { freersakey(key); sfree(key); } break; } case SSH2_AGENTC_ADD_IDENTITY: { /* * Add to the list and return SSH_AGENT_SUCCESS, or * SSH_AGENT_FAILURE if the key was malformed. */ ssh2_userkey *key = NULL; ptrlen algpl; const ssh_keyalg *alg; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_ADD_IDENTITY"); algpl = get_string(msg); key = snew(ssh2_userkey); key->key = NULL; key->comment = NULL; alg = find_pubkey_alg_len(algpl); if (!alg) { fail("algorithm unknown"); goto add2_cleanup; } key->key = ssh_key_new_priv_openssh(alg, msg); if (!key->key) { fail("key setup failed"); goto add2_cleanup; } key->comment = mkstr(get_string(msg)); if (get_err(msg)) { fail("unable to decode request"); goto add2_cleanup; } if (!pc->suppress_logging) { char *fingerprint = ssh2_fingerprint(key->key); pageant_client_log(pc, reqid, "submitted key: %s %s", fingerprint, key->comment); sfree(fingerprint); } if (pageant_add_ssh2_key(key)) { keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); key = NULL; /* don't clean it up */ } else { fail("key already present"); } add2_cleanup: if (key) { if (key->key) ssh_key_free(key->key); if (key->comment) sfree(key->comment); sfree(key); } break; } case SSH1_AGENTC_REMOVE_RSA_IDENTITY: { /* * Remove from the list and return SSH_AGENT_SUCCESS, or * perhaps SSH_AGENT_FAILURE if it wasn't in the list to * start with. */ RSAKey reqkey; PageantKey *pk; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); memset(&reqkey, 0, sizeof(reqkey)); get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); if (get_err(msg)) { fail("unable to decode request"); freersakey(&reqkey); goto responded; } if (!pc->suppress_logging) { char *fingerprint; reqkey.comment = NULL; fingerprint = rsa_ssh1_fingerprint(&reqkey); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); } pk = findkey1(&reqkey); freersakey(&reqkey); if (pk) { pageant_client_log(pc, reqid, "found with comment: %s", pk->rkey->comment); del234(keytree, pk); keylist_update(); pk_free(pk); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); } else { fail("key not found"); } break; } case SSH2_AGENTC_REMOVE_IDENTITY: { /* * Remove from the list and return SSH_AGENT_SUCCESS, or * perhaps SSH_AGENT_FAILURE if it wasn't in the list to * start with. */ PageantKey *pk; ptrlen blob; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY"); blob = get_string(msg); if (get_err(msg)) { fail("unable to decode request"); goto responded; } if (!pc->suppress_logging) { char *fingerprint = ssh2_fingerprint_blob(blob); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); } pk = findkey2(blob); if (!pk) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); del234(keytree, pk); keylist_update(); pk_free(pk); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); break; } case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES: { /* * Remove all SSH-1 keys. Always returns success. */ pageant_client_log(pc, reqid, "request:" " SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES"); remove_all_keys(1); keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); break; } case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: { /* * Remove all SSH-2 keys. Always returns success. */ pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES"); remove_all_keys(2); keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); break; } case SSH2_AGENTC_EXTENSION: { enum Extension exttype = EXT_UNKNOWN; ptrlen extname = get_string(msg); pageant_client_log(pc, reqid, "request: SSH2_AGENTC_EXTENSION \"%.*s\"", PTRLEN_PRINTF(extname)); for (size_t i = 0; i < lenof(extension_names); i++) if (ptrlen_eq_ptrlen(extname, extension_names[i])) { exttype = i; /* * For SSH_AGENTC_EXTENSION requests, the message * code SSH_AGENT_FAILURE is reserved for "I don't * recognise this extension name at all". For any * other kind of failure while processing an * extension we _do_ recognise, we must switch to * returning a different failure code, with * semantics "I understood the extension name, but * something else went wrong". */ failure_type = SSH_AGENT_EXTENSION_FAILURE; break; } switch (exttype) { case EXT_UNKNOWN: fail("unrecognised extension name '%.*s'", PTRLEN_PRINTF(extname)); break; case EXT_QUERY: /* Standard request to list the supported extensions. */ put_byte(sb, SSH_AGENT_SUCCESS); for (size_t i = 0; i < lenof(extension_names); i++) put_stringpl(sb, extension_names[i]); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS + names"); break; case EXT_ADD_PPK: { ptrlen keyfile = get_string(msg); if (get_err(msg)) { fail("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)) { fail("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) { fail("failed to decode 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 { fail("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 { fail("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); break; } case EXT_REENCRYPT: { /* * Re-encrypt a single key, in the sense of deleting * its unencrypted copy, returning it to the state of * only having the encrypted PPK form stored, so that * the next attempt to use it will have to re-prompt * for the passphrase. */ ptrlen blob = get_string(msg); if (get_err(msg)) { fail("unable to decode request"); goto responded; } if (!pc->suppress_logging) { char *fingerprint = ssh2_fingerprint_blob(blob); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); sfree(fingerprint); } PageantKey *pk = findkey2(blob); if (!pk) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); if (!reencrypt_key(pk)) { fail("this key couldn't be re-encrypted"); goto responded; } put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); break; } case EXT_REENCRYPT_ALL: { /* * Re-encrypt all keys that have an encrypted form * stored. Usually, returns success, but with a uint32 * appended indicating how many keys remain * unencrypted. The exception is if there is at least * one key in the agent and _no_ key was successfully * re-encrypted; in that situation we've done nothing, * and the client didn't _want_ us to do nothing, so * we return failure. * * (Rationale: the 'failure' message ought to be * atomic, that is, you shouldn't return failure * having made a state change.) */ unsigned nfailures = 0, nsuccesses = 0; PageantKey *pk; for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) { if (reencrypt_key(pk)) nsuccesses++; else nfailures++; } if (nsuccesses == 0 && nfailures > 0) { fail("no key could be re-encrypted"); } else { put_byte(sb, SSH_AGENT_SUCCESS); put_uint32(sb, nfailures); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS " "(%u keys re-encrypted, %u failures)", nsuccesses, nfailures); } break; } } break; } default: pageant_client_log(pc, reqid, "request: unknown message type %d", type); fail("unrecognised message"); break; } #undef fail responded:; PageantImmOp *io = snew(PageantImmOp); io->pao.vt = &immop_vtable; io->pao.info = pc->info; io->pao.cr.prev = pc->info->head.prev; io->pao.cr.next = &pc->info->head; io->pao.reqid = reqid; io->response = sb; io->crLine = 0; return &io->pao; } void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, ptrlen msgpl) { PageantAsyncOp *pao = pageant_make_op(pc, reqid, msgpl); queue_toplevel_callback(pageant_async_op_callback, pao); } void pageant_init(void) { pageant_local = true; keytree = newtree234(cmpkeys); } RSAKey *pageant_nth_ssh1_key(int i) { PageantKey *pk = index234(keytree, find_first_key_for_version(1) + i); if (pk && pk->sort.ssh_version == 1) return pk->rkey; else return NULL; } ssh2_userkey *pageant_nth_ssh2_key(int i) { PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i); if (pk && pk->sort.ssh_version == 2) return pk->skey; else return NULL; } bool pageant_delete_ssh1_key(RSAKey *rkey) { strbuf *blob = makeblob1(rkey); PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); PageantKey *deleted = del234(keytree, &sort); strbuf_free(blob); if (!deleted) return false; assert(deleted->sort.ssh_version == 1); assert(deleted->rkey == rkey); return true; } bool pageant_delete_ssh2_key(ssh2_userkey *skey) { strbuf *blob = makeblob2(skey); PageantKeySort sort = keysort(2, ptrlen_from_strbuf(blob)); PageantKey *deleted = del234(keytree, &sort); strbuf_free(blob); if (!deleted) return false; assert(deleted->sort.ssh_version == 2); assert(deleted->skey == skey); return true; } /* ---------------------------------------------------------------------- * The agent plug. */ /* * An extra coroutine macro, specific to this code which is consuming * 'const char *data'. */ #define crGetChar(c) do \ { \ while (len == 0) { \ *crLine =__LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ } while (0) struct pageant_conn_queued_response { struct pageant_conn_queued_response *next, *prev; size_t req_index; /* for indexing requests in log messages */ strbuf *sb; PageantClientRequestId reqid; }; struct pageant_conn_state { Socket *connsock; PageantListenerClient *plc; unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN]; unsigned len, got; bool real_packet; size_t conn_index; /* for indexing connections in log messages */ size_t req_index; /* for indexing requests in log messages */ int crLine; /* for coroutine in pageant_conn_receive */ struct pageant_conn_queued_response response_queue; PageantClient pc; Plug plug; }; static void pageant_conn_closing(Plug *plug, const char *error_msg, int error_code, bool calling_back) { struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); if (error_msg) pageant_listener_client_log(pc->plc, "c#%"SIZEu": error: %s", pc->conn_index, error_msg); else pageant_listener_client_log(pc->plc, "c#%"SIZEu": connection closed", pc->conn_index); sk_close(pc->connsock); pageant_unregister_client(&pc->pc); sfree(pc); } static void pageant_conn_sent(Plug *plug, size_t bufsize) { /* struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); */ /* * We do nothing here, because we expect that there won't be a * need to throttle and unthrottle the connection to an agent - * clients will typically not send many requests, and will wait * until they receive each reply before sending a new request. */ } static void pageant_conn_log(PageantClient *pc, PageantClientRequestId *reqid, const char *fmt, va_list ap) { struct pageant_conn_state *pcs = container_of(pc, struct pageant_conn_state, pc); struct pageant_conn_queued_response *qr = container_of(reqid, struct pageant_conn_queued_response, reqid); char *formatted = dupvprintf(fmt, ap); pageant_listener_client_log(pcs->plc, "c#%"SIZEu",r#%"SIZEu": %s", pcs->conn_index, qr->req_index, formatted); sfree(formatted); } static void pageant_conn_got_response( PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) { struct pageant_conn_state *pcs = container_of(pc, struct pageant_conn_state, pc); struct pageant_conn_queued_response *qr = container_of(reqid, struct pageant_conn_queued_response, reqid); qr->sb = strbuf_new_nm(); put_stringpl(qr->sb, response); while (pcs->response_queue.next != &pcs->response_queue && pcs->response_queue.next->sb) { qr = pcs->response_queue.next; sk_write(pcs->connsock, qr->sb->u, qr->sb->len); qr->next->prev = qr->prev; qr->prev->next = qr->next; strbuf_free(qr->sb); sfree(qr); } } static bool pageant_conn_ask_passphrase( PageantClient *pc, PageantClientDialogId *dlgid, const char *msg) { struct pageant_conn_state *pcs = container_of(pc, struct pageant_conn_state, pc); return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, msg); } static const PageantClientVtable pageant_connection_clientvt = { .log = pageant_conn_log, .got_response = pageant_conn_got_response, .ask_passphrase = pageant_conn_ask_passphrase, }; static void pageant_conn_receive( Plug *plug, int urgent, const char *data, size_t len) { struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); char c; crBegin(pc->crLine); while (len > 0) { pc->got = 0; while (pc->got < 4) { crGetChar(c); pc->lenbuf[pc->got++] = c; } pc->len = GET_32BIT_MSB_FIRST(pc->lenbuf); pc->got = 0; pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4); { struct pageant_conn_queued_response *qr = snew(struct pageant_conn_queued_response); qr->prev = pc->response_queue.prev; qr->next = &pc->response_queue; qr->prev->next = qr->next->prev = qr; qr->sb = NULL; qr->req_index = pc->req_index++; } if (!pc->real_packet) { /* * Send failure immediately, before consuming the packet * data. That way we notify the client reasonably early * even if the data channel has just started spewing * nonsense. */ pageant_client_log(&pc->pc, &pc->response_queue.prev->reqid, "early reply: SSH_AGENT_FAILURE " "(overlong message, length %u)", pc->len); static const unsigned char failure[] = { SSH_AGENT_FAILURE }; pageant_conn_got_response(&pc->pc, &pc->response_queue.prev->reqid, make_ptrlen(failure, lenof(failure))); } while (pc->got < pc->len) { crGetChar(c); if (pc->real_packet) pc->pktbuf[pc->got] = c; pc->got++; } if (pc->real_packet) pageant_handle_msg(&pc->pc, &pc->response_queue.prev->reqid, make_ptrlen(pc->pktbuf, pc->len)); } crFinishV; } struct pageant_listen_state { Socket *listensock; PageantListenerClient *plc; size_t conn_index; /* for indexing connections in log messages */ Plug plug; }; static void pageant_listen_closing(Plug *plug, const char *error_msg, int error_code, bool calling_back) { struct pageant_listen_state *pl = container_of( plug, struct pageant_listen_state, plug); if (error_msg) pageant_listener_client_log(pl->plc, "listening socket: error: %s", error_msg); sk_close(pl->listensock); pl->listensock = NULL; } static const PlugVtable pageant_connection_plugvt = { .closing = pageant_conn_closing, .receive = pageant_conn_receive, .sent = pageant_conn_sent, }; static int pageant_listen_accepting(Plug *plug, accept_fn_t constructor, accept_ctx_t ctx) { struct pageant_listen_state *pl = container_of( plug, struct pageant_listen_state, plug); struct pageant_conn_state *pc; const char *err; SocketPeerInfo *peerinfo; pc = snew(struct pageant_conn_state); pc->plug.vt = &pageant_connection_plugvt; pc->pc.vt = &pageant_connection_clientvt; pc->plc = pl->plc; pc->response_queue.next = pc->response_queue.prev = &pc->response_queue; pc->conn_index = pl->conn_index++; pc->req_index = 0; pc->crLine = 0; pc->connsock = constructor(ctx, &pc->plug); if ((err = sk_socket_error(pc->connsock)) != NULL) { sk_close(pc->connsock); sfree(pc); return 1; } sk_set_frozen(pc->connsock, false); peerinfo = sk_peer_info(pc->connsock); if (peerinfo && peerinfo->log_text) { pageant_listener_client_log(pl->plc, "c#%"SIZEu": new connection from %s", pc->conn_index, peerinfo->log_text); } else { pageant_listener_client_log(pl->plc, "c#%"SIZEu": new connection", pc->conn_index); } sk_free_peer_info(peerinfo); pageant_register_client(&pc->pc); return 0; } static const PlugVtable pageant_listener_plugvt = { .closing = pageant_listen_closing, .accepting = pageant_listen_accepting, }; struct pageant_listen_state *pageant_listener_new( Plug **plug, PageantListenerClient *plc) { struct pageant_listen_state *pl = snew(struct pageant_listen_state); pl->plug.vt = &pageant_listener_plugvt; pl->plc = plc; pl->listensock = NULL; pl->conn_index = 0; *plug = &pl->plug; return pl; } void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *sock) { pl->listensock = sock; } 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; typedef struct PageantInternalClient { strbuf *response; bool got_response; PageantClient pc; } PageantInternalClient; static void internal_client_got_response( PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) { PageantInternalClient *pic = container_of(pc, PageantInternalClient, pc); strbuf_clear(pic->response); put_datapl(pic->response, response); pic->got_response = true; } static bool internal_client_ask_passphrase( PageantClient *pc, PageantClientDialogId *dlgid, const char *msg) { /* No delaying operations are permitted in this mode */ return false; } static const PageantClientVtable internal_clientvt = { .log = NULL, .got_response = internal_client_got_response, .ask_passphrase = internal_client_ask_passphrase, }; typedef struct PageantClientOp { strbuf *buf; bool request_made; BinarySink_DELEGATE_IMPLEMENTATION; BinarySource_IMPLEMENTATION; } PageantClientOp; static PageantClientOp *pageant_client_op_new(void) { PageantClientOp *pco = snew(PageantClientOp); pco->buf = strbuf_new_for_agent_query(); pco->request_made = false; BinarySink_DELEGATE_INIT(pco, pco->buf); BinarySource_INIT(pco, "", 0); return pco; } static void pageant_client_op_free(PageantClientOp *pco) { if (pco->buf) strbuf_free(pco->buf); sfree(pco); } static unsigned pageant_client_op_query(PageantClientOp *pco) { /* Since we use the same strbuf for the request and the response, * check by assertion that we aren't embarrassingly sending a * previous response back to the agent */ assert(!pco->request_made); pco->request_made = true; if (!pageant_local) { void *response_raw; int resplen_raw; agent_query_synchronous(pco->buf, &response_raw, &resplen_raw); strbuf_clear(pco->buf); put_data(pco->buf, response_raw, resplen_raw); sfree(response_raw); /* The data coming back from agent_query_synchronous will have * its length field prepended. So we start by parsing it as an * SSH-formatted string, and then reinitialise our * BinarySource with the interior of that string. */ BinarySource_INIT_PL(pco, ptrlen_from_strbuf(pco->buf)); BinarySource_INIT_PL(pco, get_string(pco)); } else { PageantInternalClient pic; PageantClientRequestId reqid; pic.pc.vt = &internal_clientvt; pic.pc.suppress_logging = true; pic.response = pco->buf; pic.got_response = false; pageant_register_client(&pic.pc); assert(pco->buf->len > 4); PageantAsyncOp *pao = pageant_make_op( &pic.pc, &reqid, make_ptrlen(pco->buf->s + 4, pco->buf->len - 4)); while (!pic.got_response) pageant_async_op_coroutine(pao); pageant_unregister_client(&pic.pc); BinarySource_INIT_PL(pco, ptrlen_from_strbuf(pco->buf)); } /* Strip off and directly return the type byte, which every client * will need, to save a boilerplate get_byte at each call site */ unsigned reply_type = get_byte(pco); if (get_err(pco)) reply_type = 256; /* out-of-range code */ return reply_type; } /* * 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); sfree(pp); } } typedef struct KeyListEntry { ptrlen blob, comment; } KeyListEntry; typedef struct KeyList { strbuf *raw_data; KeyListEntry *keys; size_t nkeys; bool broken; } KeyList; static void keylist_free(KeyList *kl) { sfree(kl->keys); strbuf_free(kl->raw_data); sfree(kl); } static KeyList *pageant_get_keylist(unsigned ssh_version) { static const unsigned char requests[] = { 0, SSH1_AGENTC_REQUEST_RSA_IDENTITIES, SSH2_AGENTC_REQUEST_IDENTITIES }, responses[] = { 0, SSH1_AGENT_RSA_IDENTITIES_ANSWER, SSH2_AGENT_IDENTITIES_ANSWER }; PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, requests[ssh_version]); unsigned reply = pageant_client_op_query(pco); if (reply != responses[ssh_version]) { pageant_client_op_free(pco); return NULL; } KeyList *kl = snew(KeyList); kl->nkeys = get_uint32(pco); kl->keys = snewn(kl->nkeys, struct KeyListEntry); for (size_t i = 0; i < kl->nkeys && !get_err(pco); i++) { if (ssh_version == 1) { kl->keys[i].blob = get_data(pco, rsa_ssh1_public_blob_len( make_ptrlen(get_ptr(pco), get_avail(pco)))); } else { kl->keys[i].blob = get_string(pco); } kl->keys[i].comment = get_string(pco); } kl->broken = get_err(pco); kl->raw_data = pco->buf; pco->buf = NULL; pageant_client_op_free(pco); return kl; } int pageant_add_keyfile(Filename *filename, const char *passphrase, char **retstr, bool add_encrypted) { RSAKey *rkey = NULL; ssh2_userkey *skey = NULL; bool 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; } if (add_encrypted && type == SSH_KEYTYPE_SSH1) { *retstr = dupprintf("Can't add SSH-1 keys in encrypted form"); return PAGEANT_ACTION_FAILURE; } /* * See if the key is already loaded (in the primary Pageant, * which may or may not be us). */ { strbuf *blob = strbuf_new(); KeyList *kl; if (type == SSH_KEYTYPE_SSH1) { if (!rsa1_loadpub_f(filename, BinarySink_UPCAST(blob), NULL, &error)) { *retstr = dupprintf("Couldn't load private key (%s)", error); strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } kl = pageant_get_keylist(1); } else { if (!ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(blob), NULL, &error)) { *retstr = dupprintf("Couldn't load private key (%s)", error); strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } kl = pageant_get_keylist(2); } if (kl) { if (kl->broken) { *retstr = dupstr("Received broken key list from agent"); keylist_free(kl); strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } for (size_t i = 0; i < kl->nkeys; i++) { if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(blob), kl->keys[i].blob)) { /* Key is already present; we can now leave. */ keylist_free(kl); strbuf_free(blob); return PAGEANT_ACTION_OK; } } keylist_free(kl); } strbuf_free(blob); } if (add_encrypted) { const char *load_error; LoadedFile *lf = lf_load_keyfile(filename, &load_error); if (!lf) { *retstr = dupstr(load_error); return PAGEANT_ACTION_FAILURE; } PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_EXTENSION); put_stringpl(pco, extension_names[EXT_ADD_PPK]); put_string(pco, lf->data, lf->len); lf_free(lf); unsigned reply = pageant_client_op_query(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); return PAGEANT_ACTION_FAILURE; } return PAGEANT_ACTION_OK; } error = NULL; if (type == SSH_KEYTYPE_SSH1) needs_pass = rsa1_encrypted_f(filename, &comment); else needs_pass = ppk_encrypted_f(filename, &comment); attempts = 0; if (type == SSH_KEYTYPE_SSH1) rkey = snew(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 = rsa1_load_f(filename, rkey, this_passphrase, &error); else { skey = ppk_load_f(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); if (comment) sfree(comment); 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 successfully 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) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH1_AGENTC_ADD_RSA_IDENTITY); rsa_ssh1_private_blob_agent(BinarySink_UPCAST(pco), rkey); put_stringz(pco, rkey->comment); unsigned reply = pageant_client_op_query(pco); pageant_client_op_free(pco); freersakey(rkey); sfree(rkey); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); return PAGEANT_ACTION_FAILURE; } } else { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_ADD_IDENTITY); put_stringz(pco, ssh_key_ssh_id(skey->key)); ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(pco)); put_stringz(pco, skey->comment); unsigned reply = pageant_client_op_query(pco); pageant_client_op_free(pco); sfree(skey->comment); ssh_key_free(skey->key); sfree(skey); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); return PAGEANT_ACTION_FAILURE; } } return PAGEANT_ACTION_OK; } int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, char **retstr) { KeyList *kl1 = NULL, *kl2 = NULL; struct pageant_pubkey cbkey; int toret = PAGEANT_ACTION_FAILURE; kl1 = pageant_get_keylist(1); if (kl1 && kl1->broken) { *retstr = dupstr("Received broken SSH-1 key list from agent"); goto out; } kl2 = pageant_get_keylist(2); if (kl2 && kl2->broken) { *retstr = dupstr("Received broken SSH-2 key list from agent"); goto out; } if (kl1) { for (size_t i = 0; i < kl1->nkeys; i++) { cbkey.blob = strbuf_new(); put_datapl(cbkey.blob, kl1->keys[i].blob); cbkey.comment = mkstr(kl1->keys[i].comment); cbkey.ssh_version = 1; /* Decode public blob into a key in order to fingerprint it */ RSAKey rkey; memset(&rkey, 0, sizeof(rkey)); { BinarySource src[1]; BinarySource_BARE_INIT_PL(src, kl1->keys[i].blob); get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); if (get_err(src)) { *retstr = dupstr( "Received an invalid SSH-1 key from agent"); goto out; } } char *fingerprint = rsa_ssh1_fingerprint(&rkey); freersakey(&rkey); callback(callback_ctx, fingerprint, cbkey.comment, &cbkey); strbuf_free(cbkey.blob); sfree(cbkey.comment); sfree(fingerprint); } } if (kl2) { for (size_t i = 0; i < kl2->nkeys; i++) { cbkey.blob = strbuf_new(); put_datapl(cbkey.blob, kl2->keys[i].blob); cbkey.comment = mkstr(kl2->keys[i].comment); cbkey.ssh_version = 2; char *fingerprint = ssh2_fingerprint_blob(kl2->keys[i].blob); callback(callback_ctx, fingerprint, cbkey.comment, &cbkey); sfree(fingerprint); sfree(cbkey.comment); strbuf_free(cbkey.blob); } } *retstr = NULL; toret = PAGEANT_ACTION_OK; out: if (kl1) keylist_free(kl1); if (kl2) keylist_free(kl2); return toret; } int pageant_delete_key(struct pageant_pubkey *key, char **retstr) { PageantClientOp *pco = pageant_client_op_new(); if (key->ssh_version == 1) { put_byte(pco, SSH1_AGENTC_REMOVE_RSA_IDENTITY); put_data(pco, key->blob->s, key->blob->len); } else { put_byte(pco, SSH2_AGENTC_REMOVE_IDENTITY); put_string(pco, key->blob->s, key->blob->len); } unsigned reply = pageant_client_op_query(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to delete key"); return PAGEANT_ACTION_FAILURE; } else { *retstr = NULL; return PAGEANT_ACTION_OK; } } int pageant_delete_all_keys(char **retstr) { PageantClientOp *pco; unsigned reply; pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_REMOVE_ALL_IDENTITIES); reply = pageant_client_op_query(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to delete SSH-2 keys"); return PAGEANT_ACTION_FAILURE; } pco = pageant_client_op_new(); put_byte(pco, SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES); reply = pageant_client_op_query(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to delete SSH-1 keys"); return PAGEANT_ACTION_FAILURE; } *retstr = NULL; return PAGEANT_ACTION_OK; } int pageant_reencrypt_key(struct pageant_pubkey *key, char **retstr) { PageantClientOp *pco = pageant_client_op_new(); if (key->ssh_version == 1) { *retstr = dupstr("Can't re-encrypt an SSH-1 key"); return PAGEANT_ACTION_FAILURE; } else { put_byte(pco, SSH2_AGENTC_EXTENSION); put_stringpl(pco, extension_names[EXT_REENCRYPT]); put_string(pco, key->blob->s, key->blob->len); } unsigned reply = pageant_client_op_query(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to re-encrypt key"); return PAGEANT_ACTION_FAILURE; } else { *retstr = NULL; return PAGEANT_ACTION_OK; } } int pageant_reencrypt_all_keys(char **retstr) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_EXTENSION); put_stringpl(pco, extension_names[EXT_REENCRYPT_ALL]); unsigned reply = pageant_client_op_query(pco); uint32_t failures = get_uint32(pco); pageant_client_op_free(pco); if (reply != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to re-encrypt any keys"); return PAGEANT_ACTION_FAILURE; } else if (failures == 1) { /* special case for English grammar */ *retstr = dupstr("1 key remains unencrypted"); return PAGEANT_ACTION_WARNING; } else if (failures > 0) { *retstr = dupprintf("%"PRIu32" keys remain unencrypted", failures); return PAGEANT_ACTION_WARNING; } else { *retstr = NULL; return PAGEANT_ACTION_OK; } } int pageant_sign(struct pageant_pubkey *key, ptrlen message, strbuf *out, uint32_t flags, char **retstr) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_SIGN_REQUEST); put_string(pco, key->blob->s, key->blob->len); put_stringpl(pco, message); put_uint32(pco, flags); unsigned reply = pageant_client_op_query(pco); ptrlen signature = get_string(pco); if (reply == SSH2_AGENT_SIGN_RESPONSE && !get_err(pco)) { *retstr = NULL; put_datapl(out, signature); pageant_client_op_free(pco); return PAGEANT_ACTION_OK; } else { *retstr = dupstr("Agent failed to create signature"); pageant_client_op_free(pco); return PAGEANT_ACTION_FAILURE; } } struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) { struct pageant_pubkey *ret = snew(struct pageant_pubkey); ret->blob = strbuf_new(); put_data(ret->blob, key->blob->s, key->blob->len); 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); strbuf_free(key->blob); sfree(key); }