diff options
Diffstat (limited to 'pageant.c')
-rw-r--r-- | pageant.c | 839 |
1 files changed, 545 insertions, 294 deletions
@@ -33,8 +33,10 @@ struct PageantClientDialogId { int dummy; }; -typedef struct PageantKeySort PageantKeySort; -typedef struct PageantKey PageantKey; +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; @@ -85,33 +87,106 @@ static void pageant_async_op_callback(void *vctx) } /* - * Master list of all the keys we have stored, in any form at all. + * 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.) */ -static tree234 *keytree; -struct PageantKeySort { - /* Prefix of the main PageantKey structure which contains all the - * data that the sorting order depends on. Also simple enough that - * you can construct one for lookup purposes. */ +struct PageantPrivateKeySort { + /* + * Information used by the sorting criterion for the private key + * tree. + */ int ssh_version; /* 1 or 2; primary sort key */ - ptrlen public_blob; /* secondary 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 */ }; -struct PageantKey { - PageantKeySort sort; - strbuf *public_blob; /* the true owner of sort.public_blob */ - char *comment; /* stored separately, whether or not in rkey/skey */ +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 ssh_version == 1 */ - ssh2_userkey *skey; /* if ssh_version == 2 */ + 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 { - PageantKey *pk; + PageantPrivateKey *priv; strbuf *data_to_sign; unsigned flags; int crLine; @@ -124,47 +199,40 @@ struct PageantSignOp { /* 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(PageantKey *pk, const char *reason); -static PageantKey *pageant_nth_key(int ssh_version, int i); +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason); +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i); -static void pk_free(PageantKey *pk) +static void pk_priv_free(PageantPrivateKey *priv) { - if (pk->public_blob) strbuf_free(pk->public_blob); - sfree(pk->comment); - if (pk->sort.ssh_version == 1 && pk->rkey) { - freersakey(pk->rkey); - sfree(pk->rkey); + if (priv->base_pub) + strbuf_free(priv->base_pub); + if (priv->sort.ssh_version == 1 && priv->rkey) { + freersakey(priv->rkey); + sfree(priv->rkey); } - if (pk->sort.ssh_version == 2 && pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); + if (priv->sort.ssh_version == 2 && priv->skey) { + ssh_key_free(priv->skey); } - if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file); - fail_requests_for_key(pk, "key deleted from Pageant while signing " + 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(pk); -} - -static int cmpkeys(void *av, void *bv) -{ - PageantKeySort *a = (PageantKeySort *)av, *b = (PageantKeySort *)bv; - - if (a->ssh_version != b->ssh_version) - return a->ssh_version < b->ssh_version ? -1 : +1; - else - return ptrlen_strcmp(a->public_blob, b->public_blob); + sfree(priv); } -static inline PageantKeySort keysort(int version, ptrlen blob) +static void pk_pub_free(PageantPublicKey *pub) { - PageantKeySort sort; - sort.ssh_version = version; - sort.public_blob = blob; - return sort; + if (pub->full_pub) + strbuf_free(pub->full_pub); + sfree(pub->comment); + sfree(pub); } static strbuf *makeblob1(RSAKey *rkey) @@ -175,126 +243,308 @@ static strbuf *makeblob1(RSAKey *rkey) return blob; } -static strbuf *makeblob2(ssh2_userkey *skey) +static strbuf *makeblob2full(ssh_key *key) { strbuf *blob = strbuf_new(); - ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob)); + ssh_key_public_blob(key, BinarySink_UPCAST(blob)); return blob; } -static PageantKey *findkey1(RSAKey *reqkey) +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); - PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); - PageantKey *toret = find234(keytree, &sort, NULL); + 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; } -static PageantKey *findkey2(ptrlen blob) +/* + * 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) { - PageantKeySort sort = keysort(2, blob); - return find234(keytree, &sort, NULL); + 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_key_for_version(int ssh_version) +static int find_first_pubkey_for_version(int ssh_version) { - PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL("")); + PageantPublicKeySort sort; + sort.priv.ssh_version = ssh_version; + sort.priv.base_pub = PTRLEN_LITERAL(""); + sort.full_pub = PTRLEN_LITERAL(""); int pos; - if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos)) + if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos)) return pos; - return count234(keytree); + return count234(pubkeytree); } static int count_keys(int ssh_version) { - return (find_first_key_for_version(ssh_version + 1) - - find_first_key_for_version(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); } -static bool pageant_add_ssh1_key(RSAKey *rkey) +/* + * 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) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 1; - pk->public_blob = makeblob1(rkey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - - if (add234(keytree, pk) == pk) { - pk->rkey = rkey; - if (rkey->comment) - pk->comment = dupstr(rkey->comment); + 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 { - pk_free(pk); + /* 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) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 2; - pk->public_blob = makeblob2(skey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); - PageantKey *pk_in_tree = add234(keytree, pk); - if (pk_in_tree == pk) { - /* The key wasn't in the tree at all, and we've just added it. */ - pk->skey = skey; - if (skey->comment) - pk->comment = dupstr(skey->comment); - return true; - } else if (!pk_in_tree->skey) { - /* The key was only stored encrypted, and now we have an - * unencrypted version to add to the existing record. */ - pk_in_tree->skey = skey; - pk_free(pk); - return true; + 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 { - /* The key was already in the tree in full. */ - pk_free(pk); - return false; + /* 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_key_for_version(ssh_version); - int end = find_first_key_for_version(ssh_version + 1); + int start = find_first_pubkey_for_version(ssh_version); + int end = find_first_pubkey_for_version(ssh_version + 1); while (end > start) { - PageantKey *pk = delpos234(keytree, --end); - assert(pk->sort.ssh_version == ssh_version); - pk_free(pk); + 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; - PageantKey *pk; + PageantPublicKey *pub; put_uint32(bs, count_keys(ssh_version)); - for (i = find_first_key_for_version(ssh_version); - NULL != (pk = index234(keytree, i)); i++) { - if (pk->sort.ssh_version != ssh_version) + 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, pk->sort.public_blob); + put_stringpl(bs, pub->sort.full_pub); else - put_datapl(bs, pk->sort.public_blob); /* no header */ + put_datapl(bs, pub->sort.full_pub); /* no header */ - put_stringpl(bs, ptrlen_from_asciz(pk->comment)); + 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 @@ -303,12 +553,14 @@ static void list_keys(BinarySink *bs, int ssh_version, bool extended) * 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 (!pk->skey) + if (!priv->skey) flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; - if (pk->encrypted_key_file) + if (priv->encrypted_key_file) flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; put_uint32(sb, flags); @@ -359,13 +611,24 @@ static PRINTF_LIKE(5, 6) void failure( } } -static void signop_link(PageantSignOp *so) +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 = so->pk->blocked_requests.prev; - so->pkr.next = &so->pk->blocked_requests; + 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; } @@ -376,6 +639,7 @@ static void signop_unlink(PageantSignOp *so) 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); } @@ -388,19 +652,19 @@ static void signop_free(PageantAsyncOp *pao) sfree(so); } -static bool request_passphrase(PageantClient *pc, PageantKey *pk) +static bool request_passphrase(PageantClient *pc, PageantPrivateKey *priv) { - if (!pk->decryption_prompt_active) { + if (!priv->decryption_prompt_active) { assert(!gui_request_in_progress); bool created_dlg = pageant_client_ask_passphrase( - pc, &pk->dlgid, pk->comment); + pc, &priv->dlgid, priv->encrypted_key_comment); if (!created_dlg) return false; gui_request_in_progress = true; - pk->decryption_prompt_active = true; + priv->decryption_prompt_active = true; } return true; @@ -413,13 +677,16 @@ static void signop_coroutine(PageantAsyncOp *pao) crBegin(so->crLine); - while (!so->pk->skey && gui_request_in_progress) + while (!so->priv->skey && gui_request_in_progress) { + signop_link_to_pending_gui_request(so); crReturnV; + signop_unlink(so); + } - if (!so->pk->skey) { - assert(so->pk->encrypted_key_file); + if (!so->priv->skey) { + assert(so->priv->encrypted_key_file); - if (!request_passphrase(so->pao.info->pc, so->pk)) { + 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 " @@ -427,12 +694,12 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - signop_link(so); + signop_link_to_key(so); crReturnV; signop_unlink(so); } - uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags; + 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 @@ -445,7 +712,7 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags); + 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, @@ -455,7 +722,7 @@ static void signop_coroutine(PageantAsyncOp *pao) } strbuf *signature = strbuf_new(); - ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign), + ssh_key_sign(so->priv->skey, ptrlen_from_strbuf(so->data_to_sign), so->flags, BinarySink_UPCAST(signature)); response = strbuf_new(); @@ -476,10 +743,10 @@ static const PageantAsyncOpVtable signop_vtable = { .free = signop_free, }; -static void fail_requests_for_key(PageantKey *pk, const char *reason) +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason) { - while (pk->blocked_requests.next != &pk->blocked_requests) { - PageantSignOp *so = container_of(pk->blocked_requests.next, + 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(); @@ -492,12 +759,20 @@ static void fail_requests_for_key(PageantKey *pk, const char *reason) } } -static void unblock_requests_for_key(PageantKey *pk) +static void unblock_requests_for_key(PageantPrivateKey *priv) { - for (PageantKeyRequestNode *pkr = pk->blocked_requests.next; - pkr != &pk->blocked_requests; pkr = pkr->next) { - PageantSignOp *so = container_of(pk->blocked_requests.next, - PageantSignOp, pkr); + 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); } } @@ -505,38 +780,33 @@ static void unblock_requests_for_key(PageantKey *pk) void pageant_passphrase_request_success(PageantClientDialogId *dlgid, ptrlen passphrase) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; - if (!pk->skey) { + if (!priv->skey) { const char *error; BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - pk->encrypted_key_file)); - - strbuf *ppsb = strbuf_new_nm(); - put_datapl(ppsb, passphrase); - - pk->skey = ppk_load_s(src, ppsb->s, &error); + priv->encrypted_key_file)); + strbuf *ppsb = strbuf_dup_nm(passphrase); + ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error); strbuf_free(ppsb); - if (!pk->skey) { - fail_requests_for_key(pk, "unable to decrypt key"); + if (!skey) { + fail_requests_for_key(priv, "unable to decrypt key"); return; - } else if (pk->skey == SSH2_WRONG_PASSPHRASE) { - pk->skey = NULL; - + } else if (skey == SSH2_WRONG_PASSPHRASE) { /* * Find a PageantClient to use for another attempt at * request_passphrase. */ - PageantKeyRequestNode *pkr = pk->blocked_requests.next; - if (pkr == &pk->blocked_requests) { + 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 @@ -545,32 +815,39 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid, return; } - PageantSignOp *so = container_of(pk->blocked_requests.next, + PageantSignOp *so = container_of(priv->blocked_requests.next, PageantSignOp, pkr); - pk->decryption_prompt_active = false; - if (!request_passphrase(so->pao.info->pc, pk)) { - fail_requests_for_key(pk, "unable to continue creating " + 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(pk); + unblock_requests_for_key(priv); + + unblock_pending_gui_requests(); } void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; - fail_requests_for_key(pk, "user refused to supply passphrase"); + fail_requests_for_key(priv, "user refused to supply passphrase"); + + unblock_pending_gui_requests(); } typedef struct PageantImmOp PageantImmOp; @@ -608,9 +885,11 @@ static const PageantAsyncOpVtable immop_vtable = { .free = immop_free, }; -static bool reencrypt_key(PageantKey *pk) +static bool reencrypt_key(PageantPublicKey *pub) { - if (pk->sort.ssh_version != 2) { + 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. @@ -618,7 +897,7 @@ static bool reencrypt_key(PageantKey *pk) return false; } - if (!pk->encrypted_key_file) { + 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 @@ -627,14 +906,12 @@ static bool reencrypt_key(PageantKey *pk) return false; } - /* Only actually free pk->skey if it exists. But we return success + /* 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 (pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); - pk->skey = NULL; + if (priv->skey) { + ssh_key_free(priv->skey); + priv->skey = NULL; } return true; @@ -678,9 +955,10 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(1, i)); i++) { - char *fingerprint = rsa_ssh1_fingerprint(pk->rkey); + 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); @@ -701,12 +979,12 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); + 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, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -719,7 +997,8 @@ static PageantAsyncOp *pageant_make_op( * or not. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; + PageantPrivateKey *priv; mp_int *challenge, *response; ptrlen session_id; unsigned response_type; @@ -753,11 +1032,12 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - if ((pk = findkey1(&reqkey)) == NULL) { + if ((pub = findpubkey1(&reqkey)) == NULL) { fail("key not found"); goto challenge1_cleanup; } - response = rsa_ssh1_decrypt(challenge, pk->rkey); + priv = pub_to_priv(pub); + response = rsa_ssh1_decrypt(challenge, priv->rkey); { ssh_hash *h = ssh_hash_new(&ssh_md5); @@ -772,7 +1052,7 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE"); - challenge1_cleanup: + challenge1_cleanup: if (response) mp_free(response); mp_free(challenge); @@ -785,7 +1065,7 @@ static PageantAsyncOp *pageant_make_op( * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen keyblob, sigdata; uint32_t flags; @@ -813,12 +1093,12 @@ static PageantAsyncOp *pageant_make_op( have_flags = true; if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( keyblob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } - if ((pk = findkey2(keyblob)) == NULL) { + if ((pub = findpubkey2(keyblob)) == NULL) { fail("key not found"); goto responded; } @@ -836,11 +1116,11 @@ static PageantAsyncOp *pageant_make_op( 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->pk = pk; + so->priv = pub_to_priv(pub); so->pkr.prev = so->pkr.next = NULL; - so->data_to_sign = strbuf_new(); - put_datapl(so->data_to_sign, sigdata); + so->data_to_sign = strbuf_dup(sigdata); so->flags = flags; so->failure_type = failure_type; so->crLine = 0; @@ -885,7 +1165,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add1_cleanup: + add1_cleanup: if (key) { freersakey(key); sfree(key); @@ -946,7 +1226,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add2_cleanup: + add2_cleanup: if (key) { if (key->key) ssh_key_free(key->key); @@ -963,7 +1243,7 @@ static PageantAsyncOp *pageant_make_op( * start with. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); @@ -985,15 +1265,15 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - pk = findkey1(&reqkey); + pub = findpubkey1(&reqkey); freersakey(&reqkey); - if (pk) { + if (pub) { pageant_client_log(pc, reqid, "found with comment: %s", - pk->rkey->comment); + pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1008,7 +1288,7 @@ static PageantAsyncOp *pageant_make_op( * perhaps SSH_AGENT_FAILURE if it wasn't in the list to * start with. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen blob; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY"); @@ -1021,23 +1301,23 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); } - pk = findkey2(blob); - if (!pk) { + pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } - pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); + pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1120,22 +1400,24 @@ static PageantAsyncOp *pageant_make_op( goto responded; } + strbuf *base_pub = NULL; + strbuf *full_pub = NULL; BinarySource src[1]; const char *error; - strbuf *public_blob = strbuf_new(); + full_pub = strbuf_new(); char *comment; BinarySource_BARE_INIT_PL(src, keyfile); - if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(public_blob), + 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_fingerprint_blob( - ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT); + 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); @@ -1168,57 +1450,21 @@ static PageantAsyncOp *pageant_make_op( goto add_ppk_cleanup; } - PageantKeySort sort = - keysort(2, ptrlen_from_strbuf(public_blob)); + PageantPublicKeySort sort; + sort.priv.ssh_version = 2; + sort.full_pub = ptrlen_from_strbuf(full_pub); + base_pub = make_base_pub_2(&sort); - PageantKey *pk = find234(keytree, &sort, NULL); - if (pk) { - /* - * This public key blob already exists in the - * keytree. Add the encrypted key file to the - * existing record, if it doesn't have one already. - */ - if (!pk->encrypted_key_file) { - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log( - pc, reqid, "reply: SSH_AGENT_SUCCESS (added encrypted" - " PPK to existing key record)"); - } else { - fail("key already present"); - } - } else { - /* - * We're adding a new key record containing only - * an encrypted key file. - */ - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - pk->sort.ssh_version = 2; - pk->public_blob = public_blob; - public_blob = NULL; - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->comment = dupstr(comment); - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); - - PageantKey *added = add234(keytree, pk); - assert(added == pk); (void)added; - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS (made" - " new encrypted-only key record)"); - } + 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 (public_blob) - strbuf_free(public_blob); + add_ppk_cleanup: + if (full_pub) + strbuf_free(full_pub); + if (base_pub) + strbuf_free(base_pub); sfree(comment); break; } @@ -1239,23 +1485,23 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); sfree(fingerprint); } - PageantKey *pk = findkey2(blob); - if (!pk) { + PageantPublicKey *pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, - "found with comment: %s", pk->comment); + "found with comment: %s", pub->comment); - if (!reencrypt_key(pk)) { + if (!reencrypt_key(pub)) { fail("this key couldn't be re-encrypted"); goto responded; } @@ -1282,10 +1528,10 @@ static PageantAsyncOp *pageant_make_op( * having made a state change.) */ unsigned nfailures = 0, nsuccesses = 0; - PageantKey *pk; + PageantPublicKey *pub; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) { - if (reencrypt_key(pk)) + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) { + if (reencrypt_key(pub)) nsuccesses++; else nfailures++; @@ -1322,13 +1568,13 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH2_AGENT_SUCCESS + key list"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), + 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, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -1353,6 +1599,7 @@ static PageantAsyncOp *pageant_make_op( 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; @@ -1369,43 +1616,47 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, void pageant_init(void) { pageant_local = true; - keytree = newtree234(cmpkeys); + pubkeytree = newtree234(pubkey_cmpfn); + privkeytree = newtree234(privkey_cmpfn); } -static PageantKey *pageant_nth_key(int ssh_version, int i) +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i) { - PageantKey *pk = index234( - keytree, find_first_key_for_version(ssh_version) + i); - if (pk && pk->sort.ssh_version == ssh_version) - return pk; + 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) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(1) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(1) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_delete_nth_ssh2_key(int i) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(2) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_reencrypt_nth_ssh2_key(int i) { - PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = index234( + pubkeytree, find_first_pubkey_for_version(2) + i); + if (!pub) return false; - return reencrypt_key(pk); + return reencrypt_key(pub); } void pageant_delete_all(void) @@ -1416,9 +1667,9 @@ void pageant_delete_all(void) void pageant_reencrypt_all(void) { - PageantKey *pk; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) - reencrypt_key(pk); + PageantPublicKey *pub; + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) + reencrypt_key(pub); } /* ---------------------------------------------------------------------- @@ -1461,12 +1712,12 @@ struct pageant_conn_state { Plug plug; }; -static void pageant_conn_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) +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 (error_msg) + if (type != PLUGCLOSE_NORMAL) pageant_listener_client_log(pc->plc, "c#%"SIZEu": error: %s", pc->conn_index, error_msg); else @@ -1608,12 +1859,12 @@ struct pageant_listen_state { Plug plug; }; -static void pageant_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) +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 (error_msg) + if (type != PLUGCLOSE_NORMAL) pageant_listener_client_log(pl->plc, "listening socket: error: %s", error_msg); sk_close(pl->listensock); @@ -1624,6 +1875,7 @@ 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, @@ -1672,6 +1924,7 @@ static int pageant_listen_accepting(Plug *plug, static const PlugVtable pageant_listener_plugvt = { .closing = pageant_listen_closing, .accepting = pageant_listen_accepting, + .log = nullplug_log, }; struct pageant_listen_state *pageant_listener_new( @@ -2229,8 +2482,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl1) { for (size_t i = 0; i < kl1->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl1->keys[i].blob); + cbkey.blob = strbuf_dup(kl1->keys[i].blob); cbkey.comment = mkstr(kl1->keys[i].comment); cbkey.ssh_version = 1; @@ -2261,8 +2513,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl2) { for (size_t i = 0; i < kl2->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl2->keys[i].blob); + cbkey.blob = strbuf_dup(kl2->keys[i].blob); cbkey.comment = mkstr(kl2->keys[i].comment); cbkey.ssh_version = 2; |