12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693 |
- /*
- * 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);
- signop_unlink(so);
- 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);
- }
|