2015-05-05 22:16:19 +03:00
|
|
|
/*
|
|
|
|
* pageant.c: cross-platform code to implement Pageant.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "putty.h"
|
|
|
|
#include "ssh.h"
|
|
|
|
#include "pageant.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We need this to link with the RSA code, because rsaencrypt()
|
|
|
|
* pads its data with random bytes. Since we only use rsadecrypt()
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
int random_byte(void)
|
|
|
|
{
|
|
|
|
modalfatalbox("Internal error: attempt to use random numbers in Pageant");
|
|
|
|
exit(0);
|
|
|
|
return 0; /* unreachable, but placate optimiser */
|
|
|
|
}
|
|
|
|
|
2015-05-11 17:06:25 +03:00
|
|
|
static int pageant_local = FALSE;
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
/*
|
|
|
|
* rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys.
|
|
|
|
*/
|
|
|
|
static tree234 *rsakeys, *ssh2keys;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Blob structure for passing to the asymmetric SSH-2 key compare
|
|
|
|
* function, prototyped here.
|
|
|
|
*/
|
|
|
|
struct blob {
|
|
|
|
const unsigned char *blob;
|
|
|
|
int len;
|
|
|
|
};
|
|
|
|
static int cmpkeys_ssh2_asymm(void *av, void *bv);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Key comparison function for the 2-3-4 tree of RSA keys.
|
|
|
|
*/
|
|
|
|
static int cmpkeys_rsa(void *av, void *bv)
|
|
|
|
{
|
|
|
|
struct RSAKey *a = (struct RSAKey *) av;
|
|
|
|
struct RSAKey *b = (struct RSAKey *) bv;
|
|
|
|
Bignum am, bm;
|
|
|
|
int alen, blen;
|
|
|
|
|
|
|
|
am = a->modulus;
|
|
|
|
bm = b->modulus;
|
|
|
|
/*
|
|
|
|
* Compare by length of moduli.
|
|
|
|
*/
|
|
|
|
alen = bignum_bitcount(am);
|
|
|
|
blen = bignum_bitcount(bm);
|
|
|
|
if (alen > blen)
|
|
|
|
return +1;
|
|
|
|
else if (alen < blen)
|
|
|
|
return -1;
|
|
|
|
/*
|
|
|
|
* Now compare by moduli themselves.
|
|
|
|
*/
|
|
|
|
alen = (alen + 7) / 8; /* byte count */
|
|
|
|
while (alen-- > 0) {
|
|
|
|
int abyte, bbyte;
|
|
|
|
abyte = bignum_byte(am, alen);
|
|
|
|
bbyte = bignum_byte(bm, alen);
|
|
|
|
if (abyte > bbyte)
|
|
|
|
return +1;
|
|
|
|
else if (abyte < bbyte)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Give up.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Key comparison function for the 2-3-4 tree of SSH-2 keys.
|
|
|
|
*/
|
|
|
|
static int cmpkeys_ssh2(void *av, void *bv)
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *a = (struct ssh2_userkey *) av;
|
|
|
|
struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
|
|
|
|
int i;
|
|
|
|
int alen, blen;
|
|
|
|
unsigned char *ablob, *bblob;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare purely by public blob.
|
|
|
|
*/
|
|
|
|
ablob = a->alg->public_blob(a->data, &alen);
|
|
|
|
bblob = b->alg->public_blob(b->data, &blen);
|
|
|
|
|
|
|
|
c = 0;
|
|
|
|
for (i = 0; i < alen && i < blen; i++) {
|
|
|
|
if (ablob[i] < bblob[i]) {
|
|
|
|
c = -1;
|
|
|
|
break;
|
|
|
|
} else if (ablob[i] > bblob[i]) {
|
|
|
|
c = +1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (c == 0 && i < alen)
|
|
|
|
c = +1; /* a is longer */
|
|
|
|
if (c == 0 && i < blen)
|
|
|
|
c = -1; /* a is longer */
|
|
|
|
|
|
|
|
sfree(ablob);
|
|
|
|
sfree(bblob);
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Key comparison function for looking up a blob in the 2-3-4 tree
|
|
|
|
* of SSH-2 keys.
|
|
|
|
*/
|
|
|
|
static int cmpkeys_ssh2_asymm(void *av, void *bv)
|
|
|
|
{
|
|
|
|
struct blob *a = (struct blob *) av;
|
|
|
|
struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
|
|
|
|
int i;
|
|
|
|
int alen, blen;
|
|
|
|
const unsigned char *ablob;
|
|
|
|
unsigned char *bblob;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare purely by public blob.
|
|
|
|
*/
|
|
|
|
ablob = a->blob;
|
|
|
|
alen = a->len;
|
|
|
|
bblob = b->alg->public_blob(b->data, &blen);
|
|
|
|
|
|
|
|
c = 0;
|
|
|
|
for (i = 0; i < alen && i < blen; i++) {
|
|
|
|
if (ablob[i] < bblob[i]) {
|
|
|
|
c = -1;
|
|
|
|
break;
|
|
|
|
} else if (ablob[i] > bblob[i]) {
|
|
|
|
c = +1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (c == 0 && i < alen)
|
|
|
|
c = +1; /* a is longer */
|
|
|
|
if (c == 0 && i < blen)
|
|
|
|
c = -1; /* a is longer */
|
|
|
|
|
|
|
|
sfree(bblob);
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create an SSH-1 key list in a malloc'ed buffer; return its
|
|
|
|
* length.
|
|
|
|
*/
|
|
|
|
void *pageant_make_keylist1(int *length)
|
|
|
|
{
|
|
|
|
int i, nkeys, len;
|
|
|
|
struct RSAKey *key;
|
|
|
|
unsigned char *blob, *p, *ret;
|
|
|
|
int bloblen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Count up the number and length of keys we hold.
|
|
|
|
*/
|
|
|
|
len = 4;
|
|
|
|
nkeys = 0;
|
|
|
|
for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
|
|
|
|
nkeys++;
|
|
|
|
blob = rsa_public_blob(key, &bloblen);
|
|
|
|
len += bloblen;
|
|
|
|
sfree(blob);
|
|
|
|
len += 4 + strlen(key->comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate the buffer. */
|
|
|
|
p = ret = snewn(len, unsigned char);
|
|
|
|
if (length) *length = len;
|
|
|
|
|
|
|
|
PUT_32BIT(p, nkeys);
|
|
|
|
p += 4;
|
|
|
|
for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
|
|
|
|
blob = rsa_public_blob(key, &bloblen);
|
|
|
|
memcpy(p, blob, bloblen);
|
|
|
|
p += bloblen;
|
|
|
|
sfree(blob);
|
|
|
|
PUT_32BIT(p, strlen(key->comment));
|
|
|
|
memcpy(p + 4, key->comment, strlen(key->comment));
|
|
|
|
p += 4 + strlen(key->comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(p - ret == len);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create an SSH-2 key list in a malloc'ed buffer; return its
|
|
|
|
* length.
|
|
|
|
*/
|
|
|
|
void *pageant_make_keylist2(int *length)
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *key;
|
|
|
|
int i, len, nkeys;
|
|
|
|
unsigned char *blob, *p, *ret;
|
|
|
|
int bloblen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Count up the number and length of keys we hold.
|
|
|
|
*/
|
|
|
|
len = 4;
|
|
|
|
nkeys = 0;
|
|
|
|
for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
|
|
|
|
nkeys++;
|
|
|
|
len += 4; /* length field */
|
|
|
|
blob = key->alg->public_blob(key->data, &bloblen);
|
|
|
|
len += bloblen;
|
|
|
|
sfree(blob);
|
|
|
|
len += 4 + strlen(key->comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate the buffer. */
|
|
|
|
p = ret = snewn(len, unsigned char);
|
|
|
|
if (length) *length = len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Packet header is the obvious five bytes, plus four
|
|
|
|
* bytes for the key count.
|
|
|
|
*/
|
|
|
|
PUT_32BIT(p, nkeys);
|
|
|
|
p += 4;
|
|
|
|
for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
|
|
|
|
blob = key->alg->public_blob(key->data, &bloblen);
|
|
|
|
PUT_32BIT(p, bloblen);
|
|
|
|
p += 4;
|
|
|
|
memcpy(p, blob, bloblen);
|
|
|
|
p += bloblen;
|
|
|
|
sfree(blob);
|
|
|
|
PUT_32BIT(p, strlen(key->comment));
|
|
|
|
memcpy(p + 4, key->comment, strlen(key->comment));
|
|
|
|
p += 4 + strlen(key->comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(p - ret == len);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
|
|
|
|
#ifdef __GNUC__
|
|
|
|
__attribute__ ((format (printf, 3, 4)))
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
|
|
|
static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* This is the wrapper that takes a variadic argument list and
|
|
|
|
* turns it into the va_list that the log function really expects.
|
|
|
|
* It's safe to call this with logfn==NULL, because we
|
|
|
|
* double-check that below; but if you're going to do lots of work
|
|
|
|
* before getting here (such as looping, or hashing things) then
|
|
|
|
* you should probably check logfn manually before doing that.
|
|
|
|
*/
|
|
|
|
if (logfn) {
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
logfn(logctx, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
|
|
|
|
void *logctx, pageant_logfn_t logfn)
|
2015-05-05 22:16:19 +03:00
|
|
|
{
|
|
|
|
const unsigned char *p = msg;
|
|
|
|
const unsigned char *msgend;
|
|
|
|
unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char);
|
|
|
|
int type;
|
2015-05-06 21:32:26 +03:00
|
|
|
const char *fail_reason;
|
2015-05-05 22:16:19 +03:00
|
|
|
|
|
|
|
msgend = p + msglen;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the message type.
|
|
|
|
*/
|
2015-05-06 21:32:26 +03:00
|
|
|
if (msgend < p+1) {
|
|
|
|
fail_reason = "message contained no type code";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
type = *p++;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
|
|
|
|
/*
|
|
|
|
* Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
void *keylist;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
|
|
|
|
keylist = pageant_make_keylist1(&len);
|
|
|
|
if (len + 5 > AGENT_MAX_MSGLEN) {
|
|
|
|
sfree(keylist);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "output would exceed max msglen";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
PUT_32BIT(ret, len + 1);
|
|
|
|
memcpy(ret + 5, keylist, len);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER");
|
|
|
|
if (logfn) { /* skip this loop if not logging */
|
|
|
|
int i;
|
|
|
|
struct RSAKey *rkey;
|
|
|
|
for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
|
|
|
|
char fingerprint[128];
|
|
|
|
rsa_fingerprint(fingerprint, sizeof(fingerprint), rkey);
|
|
|
|
plog(logctx, logfn, "returned key: %s", fingerprint);
|
|
|
|
}
|
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
sfree(keylist);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SSH2_AGENTC_REQUEST_IDENTITIES:
|
|
|
|
/*
|
|
|
|
* Reply with SSH2_AGENT_IDENTITIES_ANSWER.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
void *keylist;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH2_AGENTC_REQUEST_IDENTITIES");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
|
|
|
|
keylist = pageant_make_keylist2(&len);
|
|
|
|
if (len + 5 > AGENT_MAX_MSGLEN) {
|
|
|
|
sfree(keylist);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "output would exceed max msglen";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
PUT_32BIT(ret, len + 1);
|
|
|
|
memcpy(ret + 5, keylist, len);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH2_AGENT_IDENTITIES_ANSWER");
|
|
|
|
if (logfn) { /* skip this loop if not logging */
|
|
|
|
int i;
|
|
|
|
struct ssh2_userkey *skey;
|
|
|
|
for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
|
2015-05-12 16:35:44 +03:00
|
|
|
char *fingerprint = ssh2_fingerprint(skey->alg,
|
|
|
|
skey->data);
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "returned key: %s %s",
|
|
|
|
fingerprint, skey->comment);
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
sfree(keylist);
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct RSAKey reqkey, *key;
|
|
|
|
Bignum challenge, response;
|
|
|
|
unsigned char response_source[48], response_md5[16];
|
|
|
|
struct MD5Context md5c;
|
|
|
|
int i, len;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH1_AGENTC_RSA_CHALLENGE");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
p += 4;
|
|
|
|
i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
|
2015-05-06 21:32:26 +03:00
|
|
|
if (i < 0) {
|
|
|
|
fail_reason = "request truncated before key exponent";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
p += i;
|
|
|
|
i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
|
|
|
|
if (i < 0) {
|
|
|
|
freebn(reqkey.exponent);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before key modulus";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += i;
|
|
|
|
i = ssh1_read_bignum(p, msgend - p, &challenge);
|
|
|
|
if (i < 0) {
|
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
2017-02-14 23:42:26 +03:00
|
|
|
freebn(challenge);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before challenge";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += i;
|
|
|
|
if (msgend < p+16) {
|
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
|
|
|
freebn(challenge);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before session id";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
memcpy(response_source + 32, p, 16);
|
|
|
|
p += 16;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (msgend < p+4) {
|
2015-05-05 22:16:19 +03:00
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
|
|
|
freebn(challenge);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before response type";
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
if (GET_32BIT(p) != 1) {
|
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
|
|
|
freebn(challenge);
|
|
|
|
fail_reason = "response type other than 1 not supported";
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
if (logfn) {
|
|
|
|
char fingerprint[128];
|
|
|
|
reqkey.comment = NULL;
|
|
|
|
rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
|
|
|
|
plog(logctx, logfn, "requested key: %s", fingerprint);
|
|
|
|
}
|
|
|
|
if ((key = find234(rsakeys, &reqkey, NULL)) == NULL) {
|
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
|
|
|
freebn(challenge);
|
|
|
|
fail_reason = "key not found";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
response = rsadecrypt(challenge, key);
|
|
|
|
for (i = 0; i < 32; i++)
|
|
|
|
response_source[i] = bignum_byte(response, 31 - i);
|
|
|
|
|
|
|
|
MD5Init(&md5c);
|
|
|
|
MD5Update(&md5c, response_source, 48);
|
|
|
|
MD5Final(response_md5, &md5c);
|
|
|
|
smemclr(response_source, 48); /* burn the evidence */
|
|
|
|
freebn(response); /* and that evidence */
|
|
|
|
freebn(challenge); /* yes, and that evidence */
|
|
|
|
freebn(reqkey.exponent); /* and free some memory ... */
|
|
|
|
freebn(reqkey.modulus); /* ... while we're at it. */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Packet is the obvious five byte header, plus sixteen
|
|
|
|
* bytes of MD5.
|
|
|
|
*/
|
|
|
|
len = 5 + 16;
|
|
|
|
PUT_32BIT(ret, len - 4);
|
|
|
|
ret[4] = SSH1_AGENT_RSA_RESPONSE;
|
|
|
|
memcpy(ret + 5, response_md5, 16);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH1_AGENT_RSA_RESPONSE");
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *key;
|
|
|
|
struct blob b;
|
|
|
|
const unsigned char *data;
|
|
|
|
unsigned char *signature;
|
|
|
|
int datalen, siglen, len;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH2_AGENTC_SIGN_REQUEST");
|
|
|
|
|
|
|
|
if (msgend < p+4) {
|
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
b.len = toint(GET_32BIT(p));
|
2015-05-06 21:32:26 +03:00
|
|
|
if (b.len < 0 || b.len > msgend - (p+4)) {
|
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
p += 4;
|
|
|
|
b.blob = p;
|
|
|
|
p += b.len;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (msgend < p+4) {
|
|
|
|
fail_reason = "request truncated before string to sign";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
datalen = toint(GET_32BIT(p));
|
|
|
|
p += 4;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (datalen < 0 || datalen > msgend - p) {
|
|
|
|
fail_reason = "request truncated before string to sign";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
data = p;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (logfn) {
|
2015-05-12 16:35:44 +03:00
|
|
|
char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "requested key: %s", fingerprint);
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
|
2015-05-06 21:32:26 +03:00
|
|
|
if (!key) {
|
|
|
|
fail_reason = "key not found";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
signature = key->alg->sign(key->data, (const char *)data,
|
|
|
|
datalen, &siglen);
|
|
|
|
len = 5 + 4 + siglen;
|
|
|
|
PUT_32BIT(ret, len - 4);
|
|
|
|
ret[4] = SSH2_AGENT_SIGN_RESPONSE;
|
|
|
|
PUT_32BIT(ret + 5, siglen);
|
|
|
|
memcpy(ret + 5 + 4, signature, siglen);
|
|
|
|
sfree(signature);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH2_AGENT_SIGN_RESPONSE");
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct RSAKey *key;
|
|
|
|
char *comment;
|
|
|
|
int n, commentlen;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
key = snew(struct RSAKey);
|
|
|
|
memset(key, 0, sizeof(struct RSAKey));
|
|
|
|
|
|
|
|
n = makekey(p, msgend - p, key, NULL, 1);
|
|
|
|
if (n < 0) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
|
|
|
|
n = makeprivate(p, msgend - p, key);
|
|
|
|
if (n < 0) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before private key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
|
2015-05-06 22:49:07 +03:00
|
|
|
/* SSH-1 names p and q the other way round, i.e. we have
|
|
|
|
* the inverse of p mod q and not of q mod p. We swap the
|
|
|
|
* names, because our internal RSA wants iqmp. */
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
|
|
|
|
if (n < 0) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before iqmp";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
|
2015-05-06 22:49:07 +03:00
|
|
|
n = ssh1_read_bignum(p, msgend - p, &key->q); /* p */
|
2015-05-05 22:16:19 +03:00
|
|
|
if (n < 0) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before p";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
|
2015-05-06 22:49:07 +03:00
|
|
|
n = ssh1_read_bignum(p, msgend - p, &key->p); /* q */
|
2015-05-05 22:16:19 +03:00
|
|
|
if (n < 0) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before q";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
|
|
|
|
if (msgend < p+4) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before key comment";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
commentlen = toint(GET_32BIT(p));
|
|
|
|
|
|
|
|
if (commentlen < 0 || commentlen > msgend - p) {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before key comment";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
comment = snewn(commentlen+1, char);
|
|
|
|
if (comment) {
|
|
|
|
memcpy(comment, p + 4, commentlen);
|
|
|
|
comment[commentlen] = '\0';
|
|
|
|
key->comment = comment;
|
|
|
|
}
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
if (logfn) {
|
|
|
|
char fingerprint[128];
|
|
|
|
rsa_fingerprint(fingerprint, sizeof(fingerprint), key);
|
|
|
|
plog(logctx, logfn, "submitted key: %s", fingerprint);
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
if (add234(rsakeys, key) == key) {
|
|
|
|
keylist_update();
|
2015-05-06 21:32:26 +03:00
|
|
|
PUT_32BIT(ret, 1);
|
2015-05-05 22:16:19 +03:00
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
2015-05-05 22:16:19 +03:00
|
|
|
} else {
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
fail_reason = "key already present";
|
|
|
|
goto failure;
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SSH2_AGENTC_ADD_IDENTITY:
|
|
|
|
/*
|
|
|
|
* Add to the list and return SSH_AGENT_SUCCESS, or
|
|
|
|
* SSH_AGENT_FAILURE if the key was malformed.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *key;
|
|
|
|
char *comment;
|
|
|
|
const char *alg;
|
|
|
|
int alglen, commlen;
|
|
|
|
int bloblen;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH2_AGENTC_ADD_IDENTITY");
|
2015-05-05 22:16:19 +03:00
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
if (msgend < p+4) {
|
|
|
|
fail_reason = "request truncated before key algorithm";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
alglen = toint(GET_32BIT(p));
|
|
|
|
p += 4;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (alglen < 0 || alglen > msgend - p) {
|
|
|
|
fail_reason = "request truncated before key algorithm";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
alg = (const char *)p;
|
|
|
|
p += alglen;
|
|
|
|
|
|
|
|
key = snew(struct ssh2_userkey);
|
2015-05-07 21:57:46 +03:00
|
|
|
key->alg = find_pubkey_alg_len(alglen, alg);
|
|
|
|
if (!key->alg) {
|
2015-05-05 22:16:19 +03:00
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "algorithm unknown";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
bloblen = msgend - p;
|
2015-05-15 12:12:07 +03:00
|
|
|
key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen);
|
2015-05-05 22:16:19 +03:00
|
|
|
if (!key->data) {
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "key setup failed";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* p has been advanced by openssh_createkey, but
|
|
|
|
* certainly not _beyond_ the end of the buffer.
|
|
|
|
*/
|
|
|
|
assert(p <= msgend);
|
|
|
|
|
|
|
|
if (msgend < p+4) {
|
|
|
|
key->alg->freekey(key->data);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before key comment";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
commlen = toint(GET_32BIT(p));
|
|
|
|
p += 4;
|
|
|
|
|
|
|
|
if (commlen < 0 || commlen > msgend - p) {
|
|
|
|
key->alg->freekey(key->data);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
fail_reason = "request truncated before key comment";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
comment = snewn(commlen + 1, char);
|
|
|
|
if (comment) {
|
|
|
|
memcpy(comment, p, commlen);
|
|
|
|
comment[commlen] = '\0';
|
|
|
|
}
|
|
|
|
key->comment = comment;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
if (logfn) {
|
2015-05-12 16:35:44 +03:00
|
|
|
char *fingerprint = ssh2_fingerprint(key->alg, key->data);
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "submitted key: %s %s",
|
|
|
|
fingerprint, key->comment);
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
if (add234(ssh2keys, key) == key) {
|
|
|
|
keylist_update();
|
2015-05-06 21:32:26 +03:00
|
|
|
PUT_32BIT(ret, 1);
|
2015-05-05 22:16:19 +03:00
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
2015-05-05 22:16:19 +03:00
|
|
|
} else {
|
|
|
|
key->alg->freekey(key->data);
|
|
|
|
sfree(key->comment);
|
|
|
|
sfree(key);
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
fail_reason = "key already present";
|
|
|
|
goto failure;
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct RSAKey reqkey, *key;
|
|
|
|
int n;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
n = makekey(p, msgend - p, &reqkey, NULL, 0);
|
2015-05-06 21:32:26 +03:00
|
|
|
if (n < 0) {
|
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (logfn) {
|
|
|
|
char fingerprint[128];
|
|
|
|
reqkey.comment = NULL;
|
|
|
|
rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
|
|
|
|
plog(logctx, logfn, "unwanted key: %s", fingerprint);
|
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
|
|
|
|
key = find234(rsakeys, &reqkey, NULL);
|
|
|
|
freebn(reqkey.exponent);
|
|
|
|
freebn(reqkey.modulus);
|
|
|
|
PUT_32BIT(ret, 1);
|
|
|
|
if (key) {
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "found with comment: %s", key->comment);
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
del234(rsakeys, key);
|
|
|
|
keylist_update();
|
|
|
|
freersakey(key);
|
|
|
|
sfree(key);
|
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
|
|
|
} else {
|
|
|
|
fail_reason = "key not found";
|
|
|
|
goto failure;
|
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *key;
|
|
|
|
struct blob b;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_IDENTITY");
|
|
|
|
|
|
|
|
if (msgend < p+4) {
|
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
b.len = toint(GET_32BIT(p));
|
|
|
|
p += 4;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
if (b.len < 0 || b.len > msgend - p) {
|
|
|
|
fail_reason = "request truncated before public key";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
2015-05-05 22:16:19 +03:00
|
|
|
b.blob = p;
|
|
|
|
p += b.len;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
if (logfn) {
|
2015-05-12 16:35:44 +03:00
|
|
|
char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "unwanted key: %s", fingerprint);
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
|
2015-05-06 21:32:26 +03:00
|
|
|
if (!key) {
|
|
|
|
fail_reason = "key not found";
|
2015-05-05 22:16:19 +03:00
|
|
|
goto failure;
|
2015-05-06 21:32:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
plog(logctx, logfn, "found with comment: %s", key->comment);
|
2015-05-05 22:16:19 +03:00
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
del234(ssh2keys, key);
|
|
|
|
keylist_update();
|
|
|
|
key->alg->freekey(key->data);
|
|
|
|
sfree(key);
|
2015-05-05 22:16:19 +03:00
|
|
|
PUT_32BIT(ret, 1);
|
2015-05-06 21:32:26 +03:00
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
|
|
|
|
/*
|
|
|
|
* Remove all SSH-1 keys. Always returns success.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct RSAKey *rkey;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request:"
|
|
|
|
" SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
while ((rkey = index234(rsakeys, 0)) != NULL) {
|
|
|
|
del234(rsakeys, rkey);
|
|
|
|
freersakey(rkey);
|
|
|
|
sfree(rkey);
|
|
|
|
}
|
|
|
|
keylist_update();
|
|
|
|
|
|
|
|
PUT_32BIT(ret, 1);
|
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
|
|
|
|
/*
|
|
|
|
* Remove all SSH-2 keys. Always returns success.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *skey;
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES");
|
|
|
|
|
2015-05-05 22:16:19 +03:00
|
|
|
while ((skey = index234(ssh2keys, 0)) != NULL) {
|
|
|
|
del234(ssh2keys, skey);
|
|
|
|
skey->alg->freekey(skey->data);
|
|
|
|
sfree(skey);
|
|
|
|
}
|
|
|
|
keylist_update();
|
|
|
|
|
|
|
|
PUT_32BIT(ret, 1);
|
|
|
|
ret[4] = SSH_AGENT_SUCCESS;
|
2015-05-06 21:32:26 +03:00
|
|
|
|
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "request: unknown message type %d", type);
|
|
|
|
|
|
|
|
fail_reason = "unrecognised message";
|
|
|
|
/* fall through */
|
2015-05-05 22:16:19 +03:00
|
|
|
failure:
|
|
|
|
/*
|
|
|
|
* Unrecognised message. Return SSH_AGENT_FAILURE.
|
|
|
|
*/
|
|
|
|
PUT_32BIT(ret, 1);
|
|
|
|
ret[4] = SSH_AGENT_FAILURE;
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", fail_reason);
|
2015-05-05 22:16:19 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*outlen = 4 + GET_32BIT(ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *pageant_failure_msg(int *outlen)
|
|
|
|
{
|
|
|
|
unsigned char *ret = snewn(5, unsigned char);
|
|
|
|
PUT_32BIT(ret, 1);
|
|
|
|
ret[4] = SSH_AGENT_FAILURE;
|
|
|
|
*outlen = 5;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pageant_init(void)
|
|
|
|
{
|
2015-05-11 17:06:25 +03:00
|
|
|
pageant_local = TRUE;
|
2015-05-05 22:16:19 +03:00
|
|
|
rsakeys = newtree234(cmpkeys_rsa);
|
|
|
|
ssh2keys = newtree234(cmpkeys_ssh2);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct RSAKey *pageant_nth_ssh1_key(int i)
|
|
|
|
{
|
|
|
|
return index234(rsakeys, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ssh2_userkey *pageant_nth_ssh2_key(int i)
|
|
|
|
{
|
|
|
|
return index234(ssh2keys, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_count_ssh1_keys(void)
|
|
|
|
{
|
|
|
|
return count234(rsakeys);
|
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_count_ssh2_keys(void)
|
|
|
|
{
|
|
|
|
return count234(ssh2keys);
|
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_add_ssh1_key(struct RSAKey *rkey)
|
|
|
|
{
|
2015-05-07 20:41:06 +03:00
|
|
|
return add234(rsakeys, rkey) == rkey;
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_add_ssh2_key(struct ssh2_userkey *skey)
|
|
|
|
{
|
2015-05-07 20:41:06 +03:00
|
|
|
return add234(ssh2keys, skey) == skey;
|
2015-05-05 22:16:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_delete_ssh1_key(struct RSAKey *rkey)
|
|
|
|
{
|
|
|
|
struct RSAKey *deleted = del234(rsakeys, rkey);
|
|
|
|
if (!deleted)
|
|
|
|
return FALSE;
|
|
|
|
assert(deleted == rkey);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_delete_ssh2_key(struct ssh2_userkey *skey)
|
|
|
|
{
|
|
|
|
struct ssh2_userkey *deleted = del234(ssh2keys, skey);
|
|
|
|
if (!deleted)
|
|
|
|
return FALSE;
|
|
|
|
assert(deleted == skey);
|
|
|
|
return TRUE;
|
|
|
|
}
|
2015-05-05 22:16:20 +03:00
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* The agent plug.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Coroutine macros similar to, but simplified from, those in ssh.c.
|
|
|
|
*/
|
|
|
|
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
|
|
|
|
#define crFinish(z) } *crLine = 0; return (z); }
|
|
|
|
#define crGetChar(c) do \
|
|
|
|
{ \
|
|
|
|
while (len == 0) { \
|
|
|
|
*crLine =__LINE__; return 1; case __LINE__:; \
|
|
|
|
} \
|
|
|
|
len--; \
|
|
|
|
(c) = (unsigned char)*data++; \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
struct pageant_conn_state {
|
|
|
|
const struct plug_function_table *fn;
|
|
|
|
/* the above variable absolutely *must* be the first in this structure */
|
|
|
|
|
|
|
|
Socket connsock;
|
|
|
|
void *logctx;
|
2015-05-06 21:32:26 +03:00
|
|
|
pageant_logfn_t logfn;
|
2015-05-05 22:16:20 +03:00
|
|
|
unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN];
|
|
|
|
unsigned len, got;
|
|
|
|
int real_packet;
|
|
|
|
int crLine; /* for coroutine in pageant_conn_receive */
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pageant_conn_closing(Plug plug, const char *error_msg,
|
|
|
|
int error_code, int calling_back)
|
|
|
|
{
|
|
|
|
struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (error_msg)
|
|
|
|
plog(pc->logctx, pc->logfn, "%p: error: %s", pc, error_msg);
|
|
|
|
else
|
|
|
|
plog(pc->logctx, pc->logfn, "%p: connection closed", pc);
|
2015-05-05 22:16:20 +03:00
|
|
|
sk_close(pc->connsock);
|
|
|
|
sfree(pc);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pageant_conn_sent(Plug plug, int bufsize)
|
|
|
|
{
|
|
|
|
/* struct pageant_conn_state *pc = (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.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2015-05-06 21:32:26 +03:00
|
|
|
static void pageant_conn_log(void *logctx, const char *fmt, va_list ap)
|
|
|
|
{
|
|
|
|
/* Wrapper on pc->logfn that prefixes the connection identifier */
|
|
|
|
struct pageant_conn_state *pc = (struct pageant_conn_state *)logctx;
|
|
|
|
char *formatted = dupvprintf(fmt, ap);
|
|
|
|
plog(pc->logctx, pc->logfn, "%p: %s", pc, formatted);
|
|
|
|
sfree(formatted);
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:20 +03:00
|
|
|
static int pageant_conn_receive(Plug plug, int urgent, char *data, int len)
|
|
|
|
{
|
|
|
|
struct pageant_conn_state *pc = (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(pc->lenbuf);
|
|
|
|
pc->got = 0;
|
|
|
|
pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4);
|
|
|
|
|
|
|
|
while (pc->got < pc->len) {
|
|
|
|
crGetChar(c);
|
|
|
|
if (pc->real_packet)
|
|
|
|
pc->pktbuf[pc->got] = c;
|
|
|
|
pc->got++;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
void *reply;
|
|
|
|
int replylen;
|
|
|
|
|
|
|
|
if (pc->real_packet) {
|
2015-05-06 21:32:26 +03:00
|
|
|
reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen, pc,
|
|
|
|
pc->logfn?pageant_conn_log:NULL);
|
2015-05-05 22:16:20 +03:00
|
|
|
} else {
|
2015-05-06 21:32:26 +03:00
|
|
|
plog(pc->logctx, pc->logfn, "%p: overlong message (%u)",
|
|
|
|
pc, pc->len);
|
|
|
|
plog(pc->logctx, pc->logfn, "%p: reply: SSH_AGENT_FAILURE "
|
|
|
|
"(message too long)", pc);
|
2015-05-05 22:16:20 +03:00
|
|
|
reply = pageant_failure_msg(&replylen);
|
|
|
|
}
|
|
|
|
sk_write(pc->connsock, reply, replylen);
|
|
|
|
smemclr(reply, replylen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
crFinish(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct pageant_listen_state {
|
|
|
|
const struct plug_function_table *fn;
|
|
|
|
/* the above variable absolutely *must* be the first in this structure */
|
|
|
|
|
|
|
|
Socket listensock;
|
|
|
|
void *logctx;
|
2015-05-06 21:32:26 +03:00
|
|
|
pageant_logfn_t logfn;
|
2015-05-05 22:16:20 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static int pageant_listen_closing(Plug plug, const char *error_msg,
|
|
|
|
int error_code, int calling_back)
|
|
|
|
{
|
|
|
|
struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
|
2015-05-06 21:32:26 +03:00
|
|
|
if (error_msg)
|
|
|
|
plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg);
|
2015-05-05 22:16:20 +03:00
|
|
|
sk_close(pl->listensock);
|
|
|
|
pl->listensock = NULL;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pageant_listen_accepting(Plug plug,
|
|
|
|
accept_fn_t constructor, accept_ctx_t ctx)
|
|
|
|
{
|
|
|
|
static const struct plug_function_table connection_fn_table = {
|
|
|
|
NULL, /* no log function, because that's for outgoing connections */
|
|
|
|
pageant_conn_closing,
|
|
|
|
pageant_conn_receive,
|
|
|
|
pageant_conn_sent,
|
|
|
|
NULL /* no accepting function, because we've already done it */
|
|
|
|
};
|
|
|
|
struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
|
|
|
|
struct pageant_conn_state *pc;
|
|
|
|
const char *err;
|
2015-05-18 15:57:45 +03:00
|
|
|
char *peerinfo;
|
2015-05-05 22:16:20 +03:00
|
|
|
|
|
|
|
pc = snew(struct pageant_conn_state);
|
|
|
|
pc->fn = &connection_fn_table;
|
|
|
|
pc->logfn = pl->logfn;
|
|
|
|
pc->logctx = pl->logctx;
|
|
|
|
pc->crLine = 0;
|
|
|
|
|
|
|
|
pc->connsock = constructor(ctx, (Plug) pc);
|
|
|
|
if ((err = sk_socket_error(pc->connsock)) != NULL) {
|
|
|
|
sk_close(pc->connsock);
|
|
|
|
sfree(pc);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
sk_set_frozen(pc->connsock, 0);
|
|
|
|
|
2015-05-18 15:57:45 +03:00
|
|
|
peerinfo = sk_peer_info(pc->connsock);
|
|
|
|
if (peerinfo) {
|
|
|
|
plog(pl->logctx, pl->logfn, "%p: new connection from %s",
|
|
|
|
pc, peerinfo);
|
|
|
|
} else {
|
|
|
|
plog(pl->logctx, pl->logfn, "%p: new connection", pc);
|
|
|
|
}
|
2015-05-05 22:16:20 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-07 21:04:25 +03:00
|
|
|
struct pageant_listen_state *pageant_listener_new(void)
|
2015-05-05 22:16:20 +03:00
|
|
|
{
|
|
|
|
static const struct plug_function_table listener_fn_table = {
|
|
|
|
NULL, /* no log function, because that's for outgoing connections */
|
|
|
|
pageant_listen_closing,
|
|
|
|
NULL, /* no receive function on a listening socket */
|
|
|
|
NULL, /* no sent function on a listening socket */
|
|
|
|
pageant_listen_accepting
|
|
|
|
};
|
|
|
|
|
|
|
|
struct pageant_listen_state *pl = snew(struct pageant_listen_state);
|
|
|
|
pl->fn = &listener_fn_table;
|
2015-05-07 21:04:25 +03:00
|
|
|
pl->logctx = NULL;
|
|
|
|
pl->logfn = NULL;
|
2015-05-05 22:16:20 +03:00
|
|
|
pl->listensock = NULL;
|
|
|
|
return pl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock)
|
|
|
|
{
|
|
|
|
pl->listensock = sock;
|
|
|
|
}
|
|
|
|
|
2015-05-07 21:04:25 +03:00
|
|
|
void pageant_listener_set_logfn(struct pageant_listen_state *pl,
|
|
|
|
void *logctx, pageant_logfn_t logfn)
|
|
|
|
{
|
|
|
|
pl->logctx = logctx;
|
|
|
|
pl->logfn = logfn;
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:16:20 +03:00
|
|
|
void pageant_listener_free(struct pageant_listen_state *pl)
|
|
|
|
{
|
|
|
|
if (pl->listensock)
|
|
|
|
sk_close(pl->listensock);
|
|
|
|
sfree(pl);
|
|
|
|
}
|
2015-05-11 17:06:25 +03:00
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Code to perform agent operations either as a client, or within the
|
|
|
|
* same process as the running agent.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static tree234 *passphrases = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After processing a list of filenames, we want to forget the
|
|
|
|
* passphrases.
|
|
|
|
*/
|
|
|
|
void pageant_forget_passphrases(void)
|
|
|
|
{
|
2015-05-14 11:16:26 +03:00
|
|
|
if (!passphrases) /* in case we never set it up at all */
|
|
|
|
return;
|
|
|
|
|
2015-05-11 17:06:25 +03:00
|
|
|
while (count234(passphrases) > 0) {
|
|
|
|
char *pp = index234(passphrases, 0);
|
|
|
|
smemclr(pp, strlen(pp));
|
|
|
|
delpos234(passphrases, 0);
|
|
|
|
free(pp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void *pageant_get_keylist1(int *length)
|
|
|
|
{
|
|
|
|
void *ret;
|
|
|
|
|
|
|
|
if (!pageant_local) {
|
|
|
|
unsigned char request[5], *response;
|
|
|
|
void *vresponse;
|
2017-01-29 23:24:15 +03:00
|
|
|
int resplen;
|
|
|
|
|
2015-05-11 17:06:25 +03:00
|
|
|
request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
|
2015-05-11 19:52:45 +03:00
|
|
|
PUT_32BIT(request, 1);
|
2015-05-11 17:06:25 +03:00
|
|
|
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, 5, &vresponse, &resplen);
|
2015-05-11 17:06:25 +03:00
|
|
|
response = vresponse;
|
|
|
|
if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
|
|
|
|
sfree(response);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snewn(resplen-5, unsigned char);
|
|
|
|
memcpy(ret, response+5, resplen-5);
|
|
|
|
sfree(response);
|
|
|
|
|
|
|
|
if (length)
|
|
|
|
*length = resplen-5;
|
|
|
|
} else {
|
|
|
|
ret = pageant_make_keylist1(length);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *pageant_get_keylist2(int *length)
|
|
|
|
{
|
|
|
|
void *ret;
|
|
|
|
|
|
|
|
if (!pageant_local) {
|
|
|
|
unsigned char request[5], *response;
|
|
|
|
void *vresponse;
|
2017-01-29 23:24:15 +03:00
|
|
|
int resplen;
|
2015-05-11 17:06:25 +03:00
|
|
|
|
|
|
|
request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
|
2015-05-11 19:52:45 +03:00
|
|
|
PUT_32BIT(request, 1);
|
2015-05-11 17:06:25 +03:00
|
|
|
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, 5, &vresponse, &resplen);
|
2015-05-11 17:06:25 +03:00
|
|
|
response = vresponse;
|
|
|
|
if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
|
|
|
|
sfree(response);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snewn(resplen-5, unsigned char);
|
|
|
|
memcpy(ret, response+5, resplen-5);
|
|
|
|
sfree(response);
|
|
|
|
|
|
|
|
if (length)
|
|
|
|
*length = resplen-5;
|
|
|
|
} else {
|
|
|
|
ret = pageant_make_keylist2(length);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pageant_add_keyfile(Filename *filename, const char *passphrase,
|
|
|
|
char **retstr)
|
|
|
|
{
|
|
|
|
struct RSAKey *rkey = NULL;
|
|
|
|
struct ssh2_userkey *skey = NULL;
|
|
|
|
int 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the key is already loaded (in the primary Pageant,
|
|
|
|
* which may or may not be us).
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
void *blob;
|
|
|
|
unsigned char *keylist, *p;
|
|
|
|
int i, nkeys, bloblen, keylistlen;
|
|
|
|
|
|
|
|
if (type == SSH_KEYTYPE_SSH1) {
|
|
|
|
if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
|
|
|
|
*retstr = dupprintf("Couldn't load private key (%s)", error);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
keylist = pageant_get_keylist1(&keylistlen);
|
|
|
|
} else {
|
|
|
|
unsigned char *blob2;
|
|
|
|
blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
|
|
|
|
NULL, &error);
|
|
|
|
if (!blob) {
|
|
|
|
*retstr = dupprintf("Couldn't load private key (%s)", error);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
/* For our purposes we want the blob prefixed with its length */
|
|
|
|
blob2 = snewn(bloblen+4, unsigned char);
|
|
|
|
PUT_32BIT(blob2, bloblen);
|
|
|
|
memcpy(blob2 + 4, blob, bloblen);
|
|
|
|
sfree(blob);
|
|
|
|
blob = blob2;
|
|
|
|
|
|
|
|
keylist = pageant_get_keylist2(&keylistlen);
|
|
|
|
}
|
|
|
|
if (keylist) {
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
nkeys = toint(GET_32BIT(keylist));
|
|
|
|
if (nkeys < 0) {
|
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p = keylist + 4;
|
|
|
|
keylistlen -= 4;
|
|
|
|
|
|
|
|
for (i = 0; i < nkeys; i++) {
|
|
|
|
if (!memcmp(blob, p, bloblen)) {
|
|
|
|
/* Key is already present; we can now leave. */
|
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
|
|
|
return PAGEANT_ACTION_OK;
|
|
|
|
}
|
|
|
|
/* Now skip over public blob */
|
|
|
|
if (type == SSH_KEYTYPE_SSH1) {
|
|
|
|
int n = rsa_public_blob_len(p, keylistlen);
|
|
|
|
if (n < 0) {
|
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
keylistlen -= n;
|
|
|
|
} else {
|
|
|
|
int n;
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
Tweak bounds checks in pageant_add_keyfile.
When we're going through the response from an SSH agent we asked for a
list of keys, and processing the string lengths in the SSH-2 sequence
of (public blob, comment) pairs, we were adding 4 to each string
length, and although we checked if the result came out to a negative
value (if interpreted as a signed integer) or a positive one going
beyond the end of the response buffer, we didn't check if it wrapped
round to a positive value less than 4. As a result, if an agent
returned malformed data sent a length field of 0xFFFFFFFC, the pointer
would advance no distance at all through the buffer, and the next
iteration of the loop would check the same length field again.
(However, this would only consume CPU pointlessly for a limited time,
because the outer loop up to 'nkeys' would still terminate sooner or
later. Also, I don't think this can sensibly be classed as a serious
security hazard - it's arguably a borderline DoS, but it requires a
hostile SSH _agent_ if data of that type is to be sent on purpose, and
an untrusted SSH agent is not part of the normal security model!)
2017-01-23 23:08:18 +03:00
|
|
|
n = GET_32BIT(p);
|
|
|
|
p += 4;
|
|
|
|
keylistlen -= 4;
|
|
|
|
|
|
|
|
if (n < 0 || n > keylistlen) {
|
2015-05-11 17:06:25 +03:00
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
keylistlen -= n;
|
|
|
|
}
|
|
|
|
/* Now skip over comment field */
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
Tweak bounds checks in pageant_add_keyfile.
When we're going through the response from an SSH agent we asked for a
list of keys, and processing the string lengths in the SSH-2 sequence
of (public blob, comment) pairs, we were adding 4 to each string
length, and although we checked if the result came out to a negative
value (if interpreted as a signed integer) or a positive one going
beyond the end of the response buffer, we didn't check if it wrapped
round to a positive value less than 4. As a result, if an agent
returned malformed data sent a length field of 0xFFFFFFFC, the pointer
would advance no distance at all through the buffer, and the next
iteration of the loop would check the same length field again.
(However, this would only consume CPU pointlessly for a limited time,
because the outer loop up to 'nkeys' would still terminate sooner or
later. Also, I don't think this can sensibly be classed as a serious
security hazard - it's arguably a borderline DoS, but it requires a
hostile SSH _agent_ if data of that type is to be sent on purpose, and
an untrusted SSH agent is not part of the normal security model!)
2017-01-23 23:08:18 +03:00
|
|
|
n = GET_32BIT(p);
|
|
|
|
p += 4;
|
|
|
|
keylistlen -= 4;
|
|
|
|
|
|
|
|
if (n < 0 || n > keylistlen) {
|
2015-05-11 17:06:25 +03:00
|
|
|
*retstr = dupstr("Received broken key list from agent");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(keylist);
|
|
|
|
sfree(blob);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p += n;
|
|
|
|
keylistlen -= n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(keylist);
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
error = NULL;
|
|
|
|
if (type == SSH_KEYTYPE_SSH1)
|
|
|
|
needs_pass = rsakey_encrypted(filename, &comment);
|
|
|
|
else
|
|
|
|
needs_pass = ssh2_userkey_encrypted(filename, &comment);
|
|
|
|
attempts = 0;
|
|
|
|
if (type == SSH_KEYTYPE_SSH1)
|
|
|
|
rkey = snew(struct 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;
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(rkey);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_NEED_PP;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
this_passphrase = "";
|
|
|
|
|
|
|
|
if (type == SSH_KEYTYPE_SSH1)
|
|
|
|
ret = loadrsakey(filename, rkey, this_passphrase, &error);
|
|
|
|
else {
|
|
|
|
skey = ssh2_load_userkey(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);
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(rkey);
|
2015-05-11 17:06:25 +03:00
|
|
|
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 succesfully 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) {
|
|
|
|
if (!pageant_local) {
|
|
|
|
unsigned char *request, *response;
|
|
|
|
void *vresponse;
|
2017-01-29 23:24:15 +03:00
|
|
|
int reqlen, clen, resplen;
|
2015-05-11 17:06:25 +03:00
|
|
|
|
|
|
|
clen = strlen(rkey->comment);
|
|
|
|
|
|
|
|
reqlen = 4 + 1 + /* length, message type */
|
|
|
|
4 + /* bit count */
|
|
|
|
ssh1_bignum_length(rkey->modulus) +
|
|
|
|
ssh1_bignum_length(rkey->exponent) +
|
|
|
|
ssh1_bignum_length(rkey->private_exponent) +
|
|
|
|
ssh1_bignum_length(rkey->iqmp) +
|
|
|
|
ssh1_bignum_length(rkey->p) +
|
|
|
|
ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
|
|
|
|
;
|
|
|
|
|
|
|
|
request = snewn(reqlen, unsigned char);
|
|
|
|
|
|
|
|
request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
|
|
|
|
reqlen = 5;
|
|
|
|
PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
|
|
|
|
reqlen += 4;
|
|
|
|
reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
|
|
|
|
reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
|
|
|
|
reqlen +=
|
|
|
|
ssh1_write_bignum(request + reqlen,
|
|
|
|
rkey->private_exponent);
|
|
|
|
reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
|
|
|
|
reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
|
|
|
|
reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
|
|
|
|
PUT_32BIT(request + reqlen, clen);
|
|
|
|
memcpy(request + reqlen + 4, rkey->comment, clen);
|
|
|
|
reqlen += 4 + clen;
|
|
|
|
PUT_32BIT(request, reqlen - 4);
|
|
|
|
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, reqlen, &vresponse, &resplen);
|
2015-05-11 17:06:25 +03:00
|
|
|
response = vresponse;
|
|
|
|
if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
|
|
|
|
*retstr = dupstr("The already running Pageant "
|
|
|
|
"refused to add the key.");
|
2017-02-14 23:42:26 +03:00
|
|
|
freersakey(rkey);
|
|
|
|
sfree(rkey);
|
|
|
|
sfree(request);
|
|
|
|
sfree(response);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
2017-02-14 23:42:26 +03:00
|
|
|
freersakey(rkey);
|
|
|
|
sfree(rkey);
|
2015-05-11 17:06:25 +03:00
|
|
|
sfree(request);
|
|
|
|
sfree(response);
|
|
|
|
} else {
|
|
|
|
if (!pageant_add_ssh1_key(rkey)) {
|
2017-02-14 23:42:26 +03:00
|
|
|
freersakey(rkey);
|
2015-05-11 17:06:25 +03:00
|
|
|
sfree(rkey); /* already present, don't waste RAM */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!pageant_local) {
|
|
|
|
unsigned char *request, *response;
|
|
|
|
void *vresponse;
|
2017-01-29 23:24:15 +03:00
|
|
|
int reqlen, alglen, clen, keybloblen, resplen;
|
2015-05-11 17:06:25 +03:00
|
|
|
alglen = strlen(skey->alg->name);
|
|
|
|
clen = strlen(skey->comment);
|
|
|
|
|
|
|
|
keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
|
|
|
|
|
|
|
|
reqlen = 4 + 1 + /* length, message type */
|
|
|
|
4 + alglen + /* algorithm name */
|
|
|
|
keybloblen + /* key data */
|
|
|
|
4 + clen /* comment */
|
|
|
|
;
|
|
|
|
|
|
|
|
request = snewn(reqlen, unsigned char);
|
|
|
|
|
|
|
|
request[4] = SSH2_AGENTC_ADD_IDENTITY;
|
|
|
|
reqlen = 5;
|
|
|
|
PUT_32BIT(request + reqlen, alglen);
|
|
|
|
reqlen += 4;
|
|
|
|
memcpy(request + reqlen, skey->alg->name, alglen);
|
|
|
|
reqlen += alglen;
|
|
|
|
reqlen += skey->alg->openssh_fmtkey(skey->data,
|
|
|
|
request + reqlen,
|
|
|
|
keybloblen);
|
|
|
|
PUT_32BIT(request + reqlen, clen);
|
|
|
|
memcpy(request + reqlen + 4, skey->comment, clen);
|
|
|
|
reqlen += clen + 4;
|
|
|
|
PUT_32BIT(request, reqlen - 4);
|
|
|
|
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, reqlen, &vresponse, &resplen);
|
2015-05-11 17:06:25 +03:00
|
|
|
response = vresponse;
|
|
|
|
if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
|
|
|
|
*retstr = dupstr("The already running Pageant "
|
|
|
|
"refused to add the key.");
|
2017-02-14 23:42:26 +03:00
|
|
|
sfree(request);
|
|
|
|
sfree(response);
|
2015-05-11 17:06:25 +03:00
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(request);
|
|
|
|
sfree(response);
|
|
|
|
} else {
|
|
|
|
if (!pageant_add_ssh2_key(skey)) {
|
|
|
|
skey->alg->freekey(skey->data);
|
|
|
|
sfree(skey); /* already present, don't waste RAM */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return PAGEANT_ACTION_OK;
|
|
|
|
}
|
2015-05-11 20:34:45 +03:00
|
|
|
|
|
|
|
int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
|
|
|
char **retstr)
|
|
|
|
{
|
|
|
|
unsigned char *keylist, *p;
|
|
|
|
int i, nkeys, keylistlen;
|
|
|
|
char *comment;
|
2015-05-12 15:27:33 +03:00
|
|
|
struct pageant_pubkey cbkey;
|
2015-05-11 20:34:45 +03:00
|
|
|
|
|
|
|
keylist = pageant_get_keylist1(&keylistlen);
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
nkeys = toint(GET_32BIT(keylist));
|
|
|
|
if (nkeys < 0) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p = keylist + 4;
|
|
|
|
keylistlen -= 4;
|
|
|
|
|
|
|
|
for (i = 0; i < nkeys; i++) {
|
|
|
|
struct RSAKey rkey;
|
|
|
|
char fingerprint[128];
|
|
|
|
int n;
|
|
|
|
|
|
|
|
/* public blob and fingerprint */
|
|
|
|
memset(&rkey, 0, sizeof(rkey));
|
|
|
|
n = makekey(p, keylistlen, &rkey, NULL, 0);
|
|
|
|
if (n < 0 || n > keylistlen) {
|
|
|
|
freersakey(&rkey);
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p += n, keylistlen -= n;
|
|
|
|
rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey);
|
|
|
|
|
|
|
|
/* comment */
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
freersakey(&rkey);
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
n = toint(GET_32BIT(p));
|
|
|
|
p += 4, keylistlen -= 4;
|
|
|
|
if (n < 0 || keylistlen < n) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
freersakey(&rkey);
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
comment = dupprintf("%.*s", (int)n, (const char *)p);
|
|
|
|
p += n, keylistlen -= n;
|
|
|
|
|
2015-05-12 15:27:33 +03:00
|
|
|
cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen);
|
2015-05-12 16:48:32 +03:00
|
|
|
cbkey.comment = comment;
|
2015-05-12 15:27:33 +03:00
|
|
|
cbkey.ssh_version = 1;
|
|
|
|
callback(callback_ctx, fingerprint, comment, &cbkey);
|
|
|
|
sfree(cbkey.blob);
|
2015-05-11 20:34:45 +03:00
|
|
|
freersakey(&rkey);
|
|
|
|
sfree(comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(keylist);
|
|
|
|
|
|
|
|
if (keylistlen != 0) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
keylist = pageant_get_keylist2(&keylistlen);
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
nkeys = toint(GET_32BIT(keylist));
|
|
|
|
if (nkeys < 0) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
p = keylist + 4;
|
|
|
|
keylistlen -= 4;
|
|
|
|
|
|
|
|
for (i = 0; i < nkeys; i++) {
|
|
|
|
char *fingerprint;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
/* public blob */
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
n = toint(GET_32BIT(p));
|
|
|
|
p += 4, keylistlen -= 4;
|
|
|
|
if (n < 0 || keylistlen < n) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
2015-05-12 16:35:44 +03:00
|
|
|
fingerprint = ssh2_fingerprint_blob(p, n);
|
2015-05-12 15:27:33 +03:00
|
|
|
cbkey.blob = p;
|
|
|
|
cbkey.bloblen = n;
|
2015-05-11 20:34:45 +03:00
|
|
|
p += n, keylistlen -= n;
|
|
|
|
|
|
|
|
/* comment */
|
|
|
|
if (keylistlen < 4) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(fingerprint);
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
n = toint(GET_32BIT(p));
|
|
|
|
p += 4, keylistlen -= 4;
|
|
|
|
if (n < 0 || keylistlen < n) {
|
|
|
|
*retstr = dupstr("Received broken SSH-2 key list from agent");
|
|
|
|
sfree(fingerprint);
|
|
|
|
sfree(keylist);
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
comment = dupprintf("%.*s", (int)n, (const char *)p);
|
|
|
|
p += n, keylistlen -= n;
|
|
|
|
|
2015-05-12 15:27:33 +03:00
|
|
|
cbkey.ssh_version = 2;
|
2015-05-12 16:48:32 +03:00
|
|
|
cbkey.comment = comment;
|
2015-05-12 15:27:33 +03:00
|
|
|
callback(callback_ctx, fingerprint, comment, &cbkey);
|
2015-05-11 20:34:45 +03:00
|
|
|
sfree(fingerprint);
|
|
|
|
sfree(comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(keylist);
|
|
|
|
|
|
|
|
if (keylistlen != 0) {
|
|
|
|
*retstr = dupstr("Received broken SSH-1 key list from agent");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PAGEANT_ACTION_OK;
|
|
|
|
}
|
2015-05-12 15:27:33 +03:00
|
|
|
|
|
|
|
int pageant_delete_key(struct pageant_pubkey *key, char **retstr)
|
|
|
|
{
|
|
|
|
unsigned char *request, *response;
|
|
|
|
int reqlen, resplen, ret;
|
|
|
|
void *vresponse;
|
|
|
|
|
|
|
|
if (key->ssh_version == 1) {
|
|
|
|
reqlen = 5 + key->bloblen;
|
|
|
|
request = snewn(reqlen, unsigned char);
|
|
|
|
PUT_32BIT(request, reqlen - 4);
|
|
|
|
request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY;
|
|
|
|
memcpy(request + 5, key->blob, key->bloblen);
|
|
|
|
} else {
|
|
|
|
reqlen = 9 + key->bloblen;
|
|
|
|
request = snewn(reqlen, unsigned char);
|
|
|
|
PUT_32BIT(request, reqlen - 4);
|
|
|
|
request[4] = SSH2_AGENTC_REMOVE_IDENTITY;
|
|
|
|
PUT_32BIT(request + 5, key->bloblen);
|
|
|
|
memcpy(request + 9, key->blob, key->bloblen);
|
|
|
|
}
|
|
|
|
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, reqlen, &vresponse, &resplen);
|
2015-05-12 15:27:33 +03:00
|
|
|
response = vresponse;
|
|
|
|
if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
|
|
|
|
*retstr = dupstr("Agent failed to delete key");
|
|
|
|
ret = PAGEANT_ACTION_FAILURE;
|
|
|
|
} else {
|
|
|
|
*retstr = NULL;
|
|
|
|
ret = PAGEANT_ACTION_OK;
|
|
|
|
}
|
|
|
|
sfree(request);
|
|
|
|
sfree(response);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-05-12 16:55:44 +03:00
|
|
|
int pageant_delete_all_keys(char **retstr)
|
|
|
|
{
|
|
|
|
unsigned char request[5], *response;
|
2017-01-29 23:24:15 +03:00
|
|
|
int reqlen, resplen, success;
|
2015-05-12 16:55:44 +03:00
|
|
|
void *vresponse;
|
|
|
|
|
|
|
|
PUT_32BIT(request, 1);
|
|
|
|
request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES;
|
|
|
|
reqlen = 5;
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, reqlen, &vresponse, &resplen);
|
2015-05-12 16:55:44 +03:00
|
|
|
response = vresponse;
|
|
|
|
success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
|
|
|
|
sfree(response);
|
|
|
|
if (!success) {
|
|
|
|
*retstr = dupstr("Agent failed to delete SSH-2 keys");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PUT_32BIT(request, 1);
|
|
|
|
request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES;
|
|
|
|
reqlen = 5;
|
2017-01-29 23:24:15 +03:00
|
|
|
agent_query_synchronous(request, reqlen, &vresponse, &resplen);
|
2015-05-12 16:55:44 +03:00
|
|
|
response = vresponse;
|
|
|
|
success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
|
|
|
|
sfree(response);
|
|
|
|
if (!success) {
|
|
|
|
*retstr = dupstr("Agent failed to delete SSH-1 keys");
|
|
|
|
return PAGEANT_ACTION_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
*retstr = NULL;
|
|
|
|
return PAGEANT_ACTION_OK;
|
|
|
|
}
|
|
|
|
|
2015-05-12 15:27:33 +03:00
|
|
|
struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key)
|
|
|
|
{
|
|
|
|
struct pageant_pubkey *ret = snew(struct pageant_pubkey);
|
|
|
|
ret->blob = snewn(key->bloblen, unsigned char);
|
|
|
|
memcpy(ret->blob, key->blob, key->bloblen);
|
|
|
|
ret->bloblen = key->bloblen;
|
2015-05-12 16:48:32 +03:00
|
|
|
ret->comment = key->comment ? dupstr(key->comment) : NULL;
|
2015-05-12 15:27:33 +03:00
|
|
|
ret->ssh_version = key->ssh_version;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pageant_pubkey_free(struct pageant_pubkey *key)
|
|
|
|
{
|
2015-05-12 16:48:32 +03:00
|
|
|
sfree(key->comment);
|
2015-05-12 15:27:33 +03:00
|
|
|
sfree(key->blob);
|
|
|
|
sfree(key);
|
|
|
|
}
|