1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692 |
- /*
- * pageant.c: cross-platform code to implement Pageant.
- */
- #include <stddef.h>
- #include <stdlib.h>
- #include <assert.h>
- #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);
- }
|