/* * 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 PageantPrivateKeySort PageantPrivateKeySort; typedef struct PageantPublicKeySort PageantPublicKeySort; typedef struct PageantPrivateKey PageantPrivateKey; typedef struct PageantPublicKey PageantPublicKey; 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 lists of all the keys we have stored, in any form at all. * * We store private and public keys in separate lists, because * multiple public keys can share the same private key (due to one * having a certificate and the other not, or having more than one * different certificate). And when we decrypt or re-encrypt a private * key, we don't really want to faff about doing it multiple times if * there's more than one public key it goes with. If someone tries to * re-encrypt a key to make their machine safer against unattended * access, then it would be embarrassing to find they'd forgotten to * re-encrypt the _other_ copy of it; conversely, once you've * decrypted a key, it's pointless to make someone type yet another * passphrase. * * (Causing multiple keys to become decrypted in one go isn't a * security hole in its own right, because the signatures generated by * certified and uncertified keys are identical. So an attacker * gaining access to an agent containing one encrypted and one * cleartext key with the same private half would still be *able* to * generate signatures that went with the encrypted one, even if the * agent refused to hand them out in response to the most obvious kind * of request.) */ struct PageantPrivateKeySort { /* * Information used by the sorting criterion for the private key * tree. */ int ssh_version; /* 1 or 2; primary sort key */ ptrlen base_pub; /* secondary sort key; never includes a certificate */ }; static int privkey_cmpfn(void *av, void *bv) { PageantPrivateKeySort *a = (PageantPrivateKeySort *)av; PageantPrivateKeySort *b = (PageantPrivateKeySort *)bv; if (a->ssh_version != b->ssh_version) return a->ssh_version < b->ssh_version ? -1 : +1; else return ptrlen_strcmp(a->base_pub, b->base_pub); } struct PageantPublicKeySort { /* * Information used by the sorting criterion for the public key * tree. Begins with the private key sorting criterion, so that * all the public keys sharing a private key appear adjacent in * the tree. That's a reasonably sensible order to list them in * for the user, and more importantly, it makes it easy to * discover when we're deleting the last public key that goes with * a particular private one, so as to delete that too. Easier than * messing about with fragile reference counts. */ PageantPrivateKeySort priv; ptrlen full_pub; /* may match priv.base_pub, or may include a cert */ }; static int pubkey_cmpfn(void *av, void *bv) { PageantPublicKeySort *a = (PageantPublicKeySort *)av; PageantPublicKeySort *b = (PageantPublicKeySort *)bv; int c = privkey_cmpfn(&a->priv, &b->priv); if (c) return c; else return ptrlen_strcmp(a->full_pub, b->full_pub); } struct PageantPrivateKey { PageantPrivateKeySort sort; strbuf *base_pub; /* the true owner of sort.base_pub */ union { RSAKey *rkey; /* if sort.priv.ssh_version == 1 */ ssh_key *skey; /* if sort.priv.ssh_version == 2 */ }; strbuf *encrypted_key_file; /* encrypted_key_comment stores the comment belonging to the * encrypted key file. This is used when presenting deferred * decryption prompts, because if the user had encrypted their * uncert and cert keys with different passphrases, the passphrase * prompt must reliably signal which file they're supposed to be * entering the passphrase for. */ char *encrypted_key_comment; bool decryption_prompt_active; PageantKeyRequestNode blocked_requests; PageantClientDialogId dlgid; }; static tree234 *privkeytree; struct PageantPublicKey { PageantPublicKeySort sort; strbuf *base_pub; /* the true owner of sort.priv.base_pub */ strbuf *full_pub; /* the true owner of sort.full_pub */ char *comment; }; static tree234 *pubkeytree; typedef struct PageantSignOp PageantSignOp; struct PageantSignOp { PageantPrivateKey *priv; 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 PageantKeyRequestNode requests_blocked_on_gui = { &requests_blocked_on_gui, &requests_blocked_on_gui }; static void failure(PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...); static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason); static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i); static void pk_priv_free(PageantPrivateKey *priv) { if (priv->base_pub) strbuf_free(priv->base_pub); if (priv->sort.ssh_version == 1 && priv->rkey) { freersakey(priv->rkey); sfree(priv->rkey); } if (priv->sort.ssh_version == 2 && priv->skey) { ssh_key_free(priv->skey); } if (priv->encrypted_key_file) strbuf_free(priv->encrypted_key_file); if (priv->encrypted_key_comment) sfree(priv->encrypted_key_comment); fail_requests_for_key(priv, "key deleted from Pageant while signing " "request was pending"); sfree(priv); } static void pk_pub_free(PageantPublicKey *pub) { if (pub->full_pub) strbuf_free(pub->full_pub); sfree(pub->comment); sfree(pub); } 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 *makeblob2full(ssh_key *key) { strbuf *blob = strbuf_new(); ssh_key_public_blob(key, BinarySink_UPCAST(blob)); return blob; } static strbuf *makeblob2base(ssh_key *key) { strbuf *blob = strbuf_new(); ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(blob)); return blob; } static PageantPrivateKey *pub_to_priv(PageantPublicKey *pub) { PageantPrivateKey *priv = find234(privkeytree, &pub->sort.priv, NULL); assert(priv && "Public and private trees out of sync!"); return priv; } static PageantPublicKey *findpubkey1(RSAKey *reqkey) { strbuf *blob = makeblob1(reqkey); PageantPublicKeySort sort; sort.priv.ssh_version = 1; sort.priv.base_pub = ptrlen_from_strbuf(blob); sort.full_pub = ptrlen_from_strbuf(blob); PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); strbuf_free(blob); return toret; } /* * Constructs the base_pub element of a PageantPublicKeySort, starting * from full_pub. This may involve allocating a strbuf to store it in, * which must survive until after you've finished using the resulting * PageantPublicKeySort. Hence, the strbuf (if any) is returned from * this function, and if it's non-NULL then the caller must eventually * free it. */ static strbuf *make_base_pub_2(PageantPublicKeySort *sort) { /* Start with the fallback option of making base_pub equal full_pub */ sort->priv.base_pub = sort->full_pub; /* Now reconstruct a distinct base_pub without a cert, if possible * and necessary */ strbuf *base_pub = NULL; BinarySource src[1]; BinarySource_BARE_INIT_PL(src, sort->full_pub); ptrlen algname = get_string(src); const ssh_keyalg *alg = find_pubkey_alg_len(algname); if (alg && alg->is_certificate) { ssh_key *key = ssh_key_new_pub(alg, sort->full_pub); if (key) { base_pub = strbuf_new(); ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(base_pub)); sort->priv.base_pub = ptrlen_from_strbuf(base_pub); ssh_key_free(key); } } return base_pub; /* caller must free once they're done with sort */ } static PageantPublicKey *findpubkey2(ptrlen full_pub) { PageantPublicKeySort sort; sort.priv.ssh_version = 2; sort.full_pub = full_pub; strbuf *base_pub = make_base_pub_2(&sort); PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); if (base_pub) strbuf_free(base_pub); return toret; } static int find_first_pubkey_for_version(int ssh_version) { PageantPublicKeySort sort; sort.priv.ssh_version = ssh_version; sort.priv.base_pub = PTRLEN_LITERAL(""); sort.full_pub = PTRLEN_LITERAL(""); int pos; if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos)) return pos; return count234(pubkeytree); } static int count_keys(int ssh_version) { return (find_first_pubkey_for_version(ssh_version + 1) - find_first_pubkey_for_version(ssh_version)); } int pageant_count_ssh1_keys(void) { return count_keys(1); } int pageant_count_ssh2_keys(void) { return count_keys(2); } /* * Common code to add a key to the trees. We fill in as many fields * here as we can share between SSH versions: the ptrlens in the * sorting field, the whole of pub->sort.priv, and the linked list of * blocked requests. */ static bool pageant_add_key_common(PageantPublicKey *pub, PageantPrivateKey *priv) { int ssh_version = priv->sort.ssh_version; priv->sort.base_pub = ptrlen_from_strbuf(priv->base_pub); pub->base_pub = strbuf_dup(priv->sort.base_pub); pub->sort.priv.ssh_version = priv->sort.ssh_version; pub->sort.priv.base_pub = ptrlen_from_strbuf(pub->base_pub); pub->sort.full_pub = ptrlen_from_strbuf(pub->full_pub); priv->blocked_requests.next = priv->blocked_requests.prev = &priv->blocked_requests; /* * Try to add the private key to privkeytree, or combine new parts * of it with what's already there. */ PageantPrivateKey *priv_in_tree = add234(privkeytree, priv); if (priv_in_tree == priv) { /* The key wasn't in the tree at all, and we've just added it. */ } else { /* The key was already in the tree, so we'll be freeing priv. */ if (ssh_version == 2 && priv->skey && !priv_in_tree->skey) { /* The key was only stored encrypted, and now we have an * unencrypted version to add to the existing record. */ priv_in_tree->skey = priv->skey; priv->skey = NULL; /* so pk_priv_free won't free it */ } if (ssh_version == 2 && priv->encrypted_key_file && !priv_in_tree->encrypted_key_file) { /* Conversely, the key was only stored in clear, and now * we have an encrypted version to add to it. */ priv_in_tree->encrypted_key_file = priv->encrypted_key_file; priv->encrypted_key_file = NULL; priv_in_tree->encrypted_key_comment = priv->encrypted_key_comment; priv->encrypted_key_comment = NULL; } pk_priv_free(priv); } /* * Try to add the public key. */ PageantPublicKey *pub_in_tree = add234(pubkeytree, pub); if (pub_in_tree == pub) { /* Successfully added a new key. */ return true; } else { /* This public key was already there. */ pk_pub_free(pub); return false; } } static bool pageant_add_ssh1_key(RSAKey *rkey) { PageantPublicKey *pub = snew(PageantPublicKey); memset(pub, 0, sizeof(PageantPublicKey)); PageantPrivateKey *priv = snew(PageantPrivateKey); memset(priv, 0, sizeof(PageantPrivateKey)); priv->sort.ssh_version = 1; priv->base_pub = makeblob1(rkey); pub->full_pub = makeblob1(rkey); if (rkey->comment) pub->comment = dupstr(rkey->comment); priv->rkey = snew(RSAKey); duprsakey(priv->rkey, rkey); return pageant_add_key_common(pub, priv); } static bool pageant_add_ssh2_key(ssh2_userkey *skey) { PageantPublicKey *pub = snew(PageantPublicKey); memset(pub, 0, sizeof(PageantPublicKey)); PageantPrivateKey *priv = snew(PageantPrivateKey); memset(priv, 0, sizeof(PageantPrivateKey)); priv->sort.ssh_version = 2; priv->base_pub = makeblob2base(skey->key); pub->full_pub = makeblob2full(skey->key); if (skey->comment) pub->comment = dupstr(skey->comment); /* Duplicate the ssh_key to go in priv */ { strbuf *tmp = strbuf_new_nm(); ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(tmp)); BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(tmp)); priv->skey = ssh_key_new_priv_openssh(ssh_key_alg(skey->key), src); strbuf_free(tmp); } return pageant_add_key_common(pub, priv); } static bool pageant_add_ssh2_key_encrypted(PageantPublicKeySort sort, const char *comment, ptrlen keyfile) { PageantPublicKey *pub = snew(PageantPublicKey); memset(pub, 0, sizeof(PageantPublicKey)); PageantPrivateKey *priv = snew(PageantPrivateKey); memset(priv, 0, sizeof(PageantPrivateKey)); assert(sort.priv.ssh_version == 2); priv->sort.ssh_version = sort.priv.ssh_version; priv->base_pub = strbuf_dup(sort.priv.base_pub); pub->full_pub = strbuf_dup(sort.full_pub); pub->comment = dupstr(comment); priv->encrypted_key_file = strbuf_dup_nm(keyfile); priv->encrypted_key_comment = dupstr(comment); return pageant_add_key_common(pub, priv); } static void remove_pubkey_cleanup(PageantPublicKey *pub) { /* Common function called when we've just removed a public key * from pubkeytree: we must also check whether that was the last * public key sharing a private half, and if so, remove the * corresponding private entry too. */ PageantPublicKeySort pubsearch; pubsearch.priv = pub->sort.priv; pubsearch.full_pub = PTRLEN_LITERAL(""); PageantPublicKey *pubfound = findrel234( pubkeytree, &pubsearch, NULL, REL234_GE); if (pubfound && !privkey_cmpfn(&pub->sort.priv, &pubfound->sort.priv)) { /* There's still a public key which has the same sort.priv as * the one we've just removed. We're good. */ } else { /* We've just removed the last public key of the family, so * delete the private half as well. */ PageantPrivateKey *priv = del234(privkeytree, &pub->sort.priv); assert(priv); assert(!privkey_cmpfn(&priv->sort, &pub->sort.priv)); pk_priv_free(priv); } } static PageantPublicKey *del_pubkey_pos(int pos) { PageantPublicKey *deleted = delpos234(pubkeytree, pos); remove_pubkey_cleanup(deleted); return deleted; } static void del_pubkey(PageantPublicKey *to_delete) { PageantPublicKey *deleted = del234(pubkeytree, to_delete); remove_pubkey_cleanup(deleted); } static void remove_all_keys(int ssh_version) { int start = find_first_pubkey_for_version(ssh_version); int end = find_first_pubkey_for_version(ssh_version + 1); while (end > start) { PageantPublicKey *pub = del_pubkey_pos(--end); assert(pub->sort.priv.ssh_version == ssh_version); pk_pub_free(pub); } } static void list_keys(BinarySink *bs, int ssh_version, bool extended) { int i; PageantPublicKey *pub; put_uint32(bs, count_keys(ssh_version)); for (i = find_first_pubkey_for_version(ssh_version); NULL != (pub = index234(pubkeytree, i)); i++) { if (pub->sort.priv.ssh_version != ssh_version) break; if (ssh_version > 1) put_stringpl(bs, pub->sort.full_pub); else put_datapl(bs, pub->sort.full_pub); /* no header */ put_stringpl(bs, ptrlen_from_asciz(pub->comment)); if (extended) { assert(ssh_version == 2); /* extended lists not supported in v1 */ /* * Append to each key entry a string containing extension * data. This string begins with a flags word, and may in * future contain further data if flag bits are set saying * that it does. Hence, it's wrapped in a containing * string, so that clients that only partially understand * it can still find the parts they do understand. */ PageantPrivateKey *priv = pub_to_priv(pub); strbuf *sb = strbuf_new(); uint32_t flags = 0; if (!priv->skey) flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; if (priv->encrypted_key_file) flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; put_uint32(sb, flags); put_stringsb(bs, sb); } } } void pageant_make_keylist1(BinarySink *bs) { list_keys(bs, 1, false); } void pageant_make_keylist2(BinarySink *bs) { list_keys(bs, 2, false); } void pageant_make_keylist_extended(BinarySink *bs) { list_keys(bs, 2, true); } 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_to_key(PageantSignOp *so) { assert(!so->pkr.prev); assert(!so->pkr.next); so->pkr.prev = so->priv->blocked_requests.prev; so->pkr.next = &so->priv->blocked_requests; so->pkr.prev->next = &so->pkr; so->pkr.next->prev = &so->pkr; } static void signop_link_to_pending_gui_request(PageantSignOp *so) { assert(!so->pkr.prev); assert(!so->pkr.next); so->pkr.prev = requests_blocked_on_gui.prev; so->pkr.next = &requests_blocked_on_gui; 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; so->pkr.prev = so->pkr.next = NULL; } 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, PageantPrivateKey *priv) { if (!priv->decryption_prompt_active) { assert(!gui_request_in_progress); bool created_dlg = pageant_client_ask_passphrase( pc, &priv->dlgid, priv->encrypted_key_comment); if (!created_dlg) return false; gui_request_in_progress = true; priv->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->priv->skey && gui_request_in_progress) { signop_link_to_pending_gui_request(so); crReturnV; signop_unlink(so); } if (!so->priv->skey) { assert(so->priv->encrypted_key_file); if (!request_passphrase(so->pao.info->pc, so->priv)) { 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_to_key(so); crReturnV; signop_unlink(so); } uint32_t supported_flags = ssh_key_supported_flags(so->priv->skey); 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->priv->skey, 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->priv->skey, 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(PageantPrivateKey *priv, const char *reason) { while (priv->blocked_requests.next != &priv->blocked_requests) { PageantSignOp *so = container_of(priv->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(PageantPrivateKey *priv) { for (PageantKeyRequestNode *pkr = priv->blocked_requests.next; pkr != &priv->blocked_requests; pkr = pkr->next) { PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); queue_toplevel_callback(pageant_async_op_callback, &so->pao); } } static void unblock_pending_gui_requests(void) { for (PageantKeyRequestNode *pkr = requests_blocked_on_gui.next; pkr != &requests_blocked_on_gui; pkr = pkr->next) { PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); queue_toplevel_callback(pageant_async_op_callback, &so->pao); } } void pageant_passphrase_request_success(PageantClientDialogId *dlgid, ptrlen passphrase) { PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; priv->decryption_prompt_active = false; if (!priv->skey) { const char *error; BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( priv->encrypted_key_file)); strbuf *ppsb = strbuf_dup_nm(passphrase); ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error); strbuf_free(ppsb); if (!skey) { fail_requests_for_key(priv, "unable to decrypt key"); return; } else if (skey == SSH2_WRONG_PASSPHRASE) { /* * Find a PageantClient to use for another attempt at * request_passphrase. */ PageantKeyRequestNode *pkr = priv->blocked_requests.next; if (pkr == &priv->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(priv->blocked_requests.next, PageantSignOp, pkr); priv->decryption_prompt_active = false; if (!request_passphrase(so->pao.info->pc, so->priv)) { fail_requests_for_key(priv, "unable to continue creating " "passphrase prompts"); } return; } else { priv->skey = skey->key; sfree(skey->comment); sfree(skey); keylist_update(); } } unblock_requests_for_key(priv); unblock_pending_gui_requests(); } void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) { PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; priv->decryption_prompt_active = false; fail_requests_for_key(priv, "user refused to supply passphrase"); unblock_pending_gui_requests(); } 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(PageantPublicKey *pub) { PageantPrivateKey *priv = pub_to_priv(pub); if (priv->sort.ssh_version != 2) { /* * We don't support storing SSH-1 keys in encrypted form at * all. */ return false; } if (!priv->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 priv->skey if it exists. But we return success * regardless, so that 'please ensure this key isn't stored * decrypted' is idempotent. */ if (priv->skey) { ssh_key_free(priv->skey); priv->skey = NULL; } return true; } #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; PageantPublicKey *pub; for (i = 0; NULL != (pub = pageant_nth_pubkey(1, i)); i++) { PageantPrivateKey *priv = pub_to_priv(pub); char *fingerprint = rsa_ssh1_fingerprint(priv->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; PageantPublicKey *pub; for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { char *fingerprint = ssh2_double_fingerprint_blob( pub->sort.full_pub, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", fingerprint, pub->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; PageantPublicKey *pub; PageantPrivateKey *priv; 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 ((pub = findpubkey1(&reqkey)) == NULL) { fail("key not found"); goto challenge1_cleanup; } priv = pub_to_priv(pub); response = rsa_ssh1_decrypt(challenge, priv->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. */ PageantPublicKey *pub; 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_double_fingerprint_blob( keyblob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } if ((pub = findpubkey2(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.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr; so->pao.reqid = reqid; so->priv = pub_to_priv(pub); so->pkr.prev = so->pkr.next = NULL; so->data_to_sign = strbuf_dup(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, SSH_FPTYPE_DEFAULT); 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; PageantPublicKey *pub; 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); } pub = findpubkey1(&reqkey); freersakey(&reqkey); if (pub) { pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); del_pubkey(pub); keylist_update(); pk_pub_free(pub); 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. */ PageantPublicKey *pub; 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_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); } pub = findpubkey2(blob); if (!pub) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); del_pubkey(pub); keylist_update(); pk_pub_free(pub); 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; } strbuf *base_pub = NULL; strbuf *full_pub = NULL; BinarySource src[1]; const char *error; full_pub = strbuf_new(); char *comment; BinarySource_BARE_INIT_PL(src, keyfile); if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(full_pub), &comment, &error)) { fail("failed to extract public key blob: %s", error); goto add_ppk_cleanup; } if (!pc->suppress_logging) { char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(full_pub), SSH_FPTYPE_DEFAULT); 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; } PageantPublicKeySort sort; sort.priv.ssh_version = 2; sort.full_pub = ptrlen_from_strbuf(full_pub); base_pub = make_base_pub_2(&sort); pageant_add_ssh2_key_encrypted(sort, comment, keyfile); keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); add_ppk_cleanup: if (full_pub) strbuf_free(full_pub); if (base_pub) strbuf_free(base_pub); 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_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); sfree(fingerprint); } PageantPublicKey *pub = findpubkey2(blob); if (!pub) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); if (!reencrypt_key(pub)) { fail("this key couldn't be re-encrypted"); goto responded; } keylist_update(); 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; PageantPublicKey *pub; for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) { if (reencrypt_key(pub)) nsuccesses++; else nfailures++; } if (nsuccesses == 0 && nfailures > 0) { fail("no key could be re-encrypted"); } else { keylist_update(); 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; } case EXT_LIST_EXTENDED: { /* * Return a key list like SSH2_AGENTC_REQUEST_IDENTITIES, * except that each key is annotated with extra * information such as whether it's currently encrypted. * * The return message type is AGENT_SUCCESS with auxiliary * data, which is more like other extension messages. I * think it would be confusing to reuse IDENTITIES_ANSWER * for a reply message with an incompatible format. */ put_byte(sb, SSH_AGENT_SUCCESS); pageant_make_keylist_extended(BinarySink_UPCAST(sb)); pageant_client_log(pc, reqid, "reply: SSH2_AGENT_SUCCESS + key list"); if (!pc->suppress_logging) { int i; PageantPublicKey *pub; for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(pub->full_pub), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", fingerprint, pub->comment); sfree(fingerprint); } } 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.cr.prev->next = io->pao.cr.next->prev = &io->pao.cr; 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; pubkeytree = newtree234(pubkey_cmpfn); privkeytree = newtree234(privkey_cmpfn); } static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i) { PageantPublicKey *pub = index234( pubkeytree, find_first_pubkey_for_version(ssh_version) + i); if (pub && pub->sort.priv.ssh_version == ssh_version) return pub; else return NULL; } bool pageant_delete_nth_ssh1_key(int i) { PageantPublicKey *pub = del_pubkey_pos( find_first_pubkey_for_version(1) + i); if (!pub) return false; pk_pub_free(pub); return true; } bool pageant_delete_nth_ssh2_key(int i) { PageantPublicKey *pub = del_pubkey_pos( find_first_pubkey_for_version(2) + i); if (!pub) return false; pk_pub_free(pub); return true; } bool pageant_reencrypt_nth_ssh2_key(int i) { PageantPublicKey *pub = index234( pubkeytree, find_first_pubkey_for_version(2) + i); if (!pub) return false; return reencrypt_key(pub); } void pageant_delete_all(void) { remove_all_keys(1); remove_all_keys(2); } void pageant_reencrypt_all(void) { PageantPublicKey *pub; for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) reencrypt_key(pub); } /* ---------------------------------------------------------------------- * 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, PlugCloseType type, const char *error_msg) { struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); if (type != PLUGCLOSE_NORMAL) 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 *comment) { struct pageant_conn_state *pcs = container_of(pc, struct pageant_conn_state, pc); return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, comment); } 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, PlugCloseType type, const char *error_msg) { struct pageant_listen_state *pl = container_of( plug, struct pageant_listen_state, plug); if (type != PLUGCLOSE_NORMAL) 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, .log = nullplug_log, }; 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; SocketEndpointInfo *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_endpoint_info(peerinfo); pageant_register_client(&pc->pc); return 0; } static const PlugVtable pageant_listener_plugvt = { .closing = pageant_listen_closing, .accepting = pageant_listen_accepting, .log = nullplug_log, }; 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 *comment) { /* 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; uint32_t flags; } 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 PageantClientOp *pageant_request_keylist_1(void) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); if (pageant_client_op_query(pco) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) return pco; pageant_client_op_free(pco); return NULL; } static PageantClientOp *pageant_request_keylist_2(void) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_REQUEST_IDENTITIES); if (pageant_client_op_query(pco) == SSH2_AGENT_IDENTITIES_ANSWER) return pco; pageant_client_op_free(pco); return NULL; } static PageantClientOp *pageant_request_keylist_extended(void) { PageantClientOp *pco = pageant_client_op_new(); put_byte(pco, SSH2_AGENTC_EXTENSION); put_stringpl(pco, extension_names[EXT_LIST_EXTENDED]); if (pageant_client_op_query(pco) == SSH_AGENT_SUCCESS) return pco; pageant_client_op_free(pco); return NULL; } static KeyList *pageant_get_keylist(unsigned ssh_version) { PageantClientOp *pco; bool list_is_extended = false; if (ssh_version == 1) { pco = pageant_request_keylist_1(); } else { if ((pco = pageant_request_keylist_extended()) != NULL) list_is_extended = true; else pco = pageant_request_keylist_2(); } if (!pco) return NULL; KeyList *kl = snew(KeyList); kl->nkeys = get_uint32(pco); kl->keys = snewn(kl->nkeys, struct KeyListEntry); kl->broken = false; for (size_t i = 0; i < kl->nkeys && !get_err(pco); i++) { if (ssh_version == 1) { int bloblen = rsa_ssh1_public_blob_len( make_ptrlen(get_ptr(pco), get_avail(pco))); if (bloblen < 0) { kl->broken = true; bloblen = 0; } kl->keys[i].blob = get_data(pco, bloblen); } else { kl->keys[i].blob = get_string(pco); } kl->keys[i].comment = get_string(pco); if (list_is_extended) { ptrlen key_ext_info = get_string(pco); BinarySource src[1]; BinarySource_BARE_INIT_PL(src, key_ext_info); kl->keys[i].flags = get_uint32(src); } else { kl->keys[i].flags = 0; } } if (get_err(pco)) kl->broken = true; 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 the key already exists in the agent, we're done, * except in the following special cases: * * It's encrypted in the agent, and we're being asked * to add it unencrypted, in which case we still want * to upload the unencrypted version to cause the key * to become decrypted. * (Rationale: if you know in advance you're going to * want it, and don't want to be interrupted at an * unpredictable moment to be asked for the * passphrase.) * * The agent only has cleartext, and we're being asked * to add it encrypted, in which case we'll add the * encrypted form. * (Rationale: if you might want to re-encrypt the key * at some future point, but it happened to have been * initially added in cleartext, perhaps by something * other than Pageant.) */ if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(blob), kl->keys[i].blob)) { bool have_unencrypted = !(kl->keys[i].flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY); bool have_encrypted = (kl->keys[i].flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE); if ((have_unencrypted && !add_encrypted) || (have_encrypted && add_encrypted)) { /* Key is already present in the desired form; * 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) { if (reply == SSH_AGENT_FAILURE) { /* The agent didn't understand the protocol extension * at all. */ *retstr = dupstr("Agent doesn't support adding " "encrypted keys"); } else { *retstr = dupstr("The already running agent " "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 agent " "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 agent " "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_dup(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 **fingerprints = rsa_ssh1_fake_all_fingerprints(&rkey); freersakey(&rkey); callback(callback_ctx, fingerprints, cbkey.comment, kl1->keys[i].flags, &cbkey); strbuf_free(cbkey.blob); sfree(cbkey.comment); ssh2_free_all_fingerprints(fingerprints); } } if (kl2) { for (size_t i = 0; i < kl2->nkeys; i++) { cbkey.blob = strbuf_dup(kl2->keys[i].blob); cbkey.comment = mkstr(kl2->keys[i].comment); cbkey.ssh_version = 2; char **fingerprints = ssh2_all_fingerprints_for_blob(kl2->keys[i].blob); callback(callback_ctx, fingerprints, cbkey.comment, kl2->keys[i].flags, &cbkey); ssh2_free_all_fingerprints(fingerprints); 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"); pageant_client_op_free(pco); 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) { if (reply == SSH_AGENT_FAILURE) { /* The agent didn't understand the protocol extension at all. */ *retstr = dupstr("Agent doesn't support encrypted keys"); } else { *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) { if (reply == SSH_AGENT_FAILURE) { /* The agent didn't understand the protocol extension at all. */ *retstr = dupstr("Agent doesn't support encrypted keys"); } else { *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 *orig) { struct pageant_pubkey *copy = snew(struct pageant_pubkey); copy->blob = strbuf_new(); put_data(copy->blob, orig->blob->s, orig->blob->len); copy->comment = orig->comment ? dupstr(orig->comment) : NULL; copy->ssh_version = orig->ssh_version; return copy; } void pageant_pubkey_free(struct pageant_pubkey *key) { sfree(key->comment); strbuf_free(key->blob); sfree(key); }