1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-25 01:02:24 +00:00
putty-source/pageant.c
Simon Tatham 5ba2d611f9 Move half of Pageant out into a cross-platform source file.
I'm aiming for windows/winpgnt.c to only contain the parts of Windows
Pageant that are actually to do with handling the Windows API, and for
all the actual agent logic to be cross-platform.

This commit is a start: I've moved every function and internal
variable that was easy to move. But it doesn't get all the way there -
there's still a lot of logic in add_keyfile() and get_keylist*() that
would be good to move out to cross-platform code, but it's harder
because that code is currently quite intertwined with details of
Windows OS interfacing such as printing message boxes and passphrase
prompts and calling back out to agent_query if the Pageant doing that
job isn't the primary one.
2015-05-05 20:16:19 +01:00

768 lines
17 KiB
C

/*
* 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 */
}
/*
* 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;
}
void *pageant_handle_msg(const void *msg, int msglen, int *outlen)
{
const unsigned char *p = msg;
const unsigned char *msgend;
unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char);
int type;
msgend = p + msglen;
/*
* Get the message type.
*/
if (msgend < p+1)
goto failure;
type = *p++;
switch (type) {
case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
/*
* Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
*/
{
int len;
void *keylist;
ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
keylist = pageant_make_keylist1(&len);
if (len + 5 > AGENT_MAX_MSGLEN) {
sfree(keylist);
goto failure;
}
PUT_32BIT(ret, len + 1);
memcpy(ret + 5, keylist, len);
sfree(keylist);
}
break;
case SSH2_AGENTC_REQUEST_IDENTITIES:
/*
* Reply with SSH2_AGENT_IDENTITIES_ANSWER.
*/
{
int len;
void *keylist;
ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
keylist = pageant_make_keylist2(&len);
if (len + 5 > AGENT_MAX_MSGLEN) {
sfree(keylist);
goto failure;
}
PUT_32BIT(ret, len + 1);
memcpy(ret + 5, keylist, len);
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;
p += 4;
i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
if (i < 0)
goto failure;
p += i;
i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
if (i < 0) {
freebn(reqkey.exponent);
goto failure;
}
p += i;
i = ssh1_read_bignum(p, msgend - p, &challenge);
if (i < 0) {
freebn(reqkey.exponent);
freebn(reqkey.modulus);
goto failure;
}
p += i;
if (msgend < p+16) {
freebn(reqkey.exponent);
freebn(reqkey.modulus);
freebn(challenge);
goto failure;
}
memcpy(response_source + 32, p, 16);
p += 16;
if (msgend < p+4 ||
GET_32BIT(p) != 1 ||
(key = find234(rsakeys, &reqkey, NULL)) == NULL) {
freebn(reqkey.exponent);
freebn(reqkey.modulus);
freebn(challenge);
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);
}
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;
if (msgend < p+4)
goto failure;
b.len = toint(GET_32BIT(p));
if (b.len < 0 || b.len > msgend - (p+4))
goto failure;
p += 4;
b.blob = p;
p += b.len;
if (msgend < p+4)
goto failure;
datalen = toint(GET_32BIT(p));
p += 4;
if (datalen < 0 || datalen > msgend - p)
goto failure;
data = p;
key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
if (!key)
goto failure;
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);
}
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;
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);
goto failure;
}
p += n;
n = makeprivate(p, msgend - p, key);
if (n < 0) {
freersakey(key);
sfree(key);
goto failure;
}
p += n;
n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
if (n < 0) {
freersakey(key);
sfree(key);
goto failure;
}
p += n;
n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
if (n < 0) {
freersakey(key);
sfree(key);
goto failure;
}
p += n;
n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
if (n < 0) {
freersakey(key);
sfree(key);
goto failure;
}
p += n;
if (msgend < p+4) {
freersakey(key);
sfree(key);
goto failure;
}
commentlen = toint(GET_32BIT(p));
if (commentlen < 0 || commentlen > msgend - p) {
freersakey(key);
sfree(key);
goto failure;
}
comment = snewn(commentlen+1, char);
if (comment) {
memcpy(comment, p + 4, commentlen);
comment[commentlen] = '\0';
key->comment = comment;
}
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_FAILURE;
if (add234(rsakeys, key) == key) {
keylist_update();
ret[4] = SSH_AGENT_SUCCESS;
} else {
freersakey(key);
sfree(key);
}
}
break;
case SSH2_AGENTC_ADD_IDENTITY:
/*
* Add to the list and return SSH_AGENT_SUCCESS, or
* SSH_AGENT_FAILURE if the key was malformed.
*/
{
struct ssh2_userkey *key;
char *comment;
const char *alg;
int alglen, commlen;
int bloblen;
if (msgend < p+4)
goto failure;
alglen = toint(GET_32BIT(p));
p += 4;
if (alglen < 0 || alglen > msgend - p)
goto failure;
alg = (const char *)p;
p += alglen;
key = snew(struct ssh2_userkey);
/* Add further algorithm names here. */
if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
key->alg = &ssh_rsa;
else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
key->alg = &ssh_dss;
else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19))
key->alg = &ssh_ecdsa_nistp256;
else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19))
key->alg = &ssh_ecdsa_nistp384;
else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19))
key->alg = &ssh_ecdsa_nistp521;
else {
sfree(key);
goto failure;
}
bloblen = msgend - p;
key->data = key->alg->openssh_createkey(&p, &bloblen);
if (!key->data) {
sfree(key);
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);
goto failure;
}
commlen = toint(GET_32BIT(p));
p += 4;
if (commlen < 0 || commlen > msgend - p) {
key->alg->freekey(key->data);
sfree(key);
goto failure;
}
comment = snewn(commlen + 1, char);
if (comment) {
memcpy(comment, p, commlen);
comment[commlen] = '\0';
}
key->comment = comment;
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_FAILURE;
if (add234(ssh2keys, key) == key) {
keylist_update();
ret[4] = SSH_AGENT_SUCCESS;
} else {
key->alg->freekey(key->data);
sfree(key->comment);
sfree(key);
}
}
break;
case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
/*
* Remove from the list and return SSH_AGENT_SUCCESS, or
* perhaps SSH_AGENT_FAILURE if it wasn't in the list to
* start with.
*/
{
struct RSAKey reqkey, *key;
int n;
n = makekey(p, msgend - p, &reqkey, NULL, 0);
if (n < 0)
goto failure;
key = find234(rsakeys, &reqkey, NULL);
freebn(reqkey.exponent);
freebn(reqkey.modulus);
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_FAILURE;
if (key) {
del234(rsakeys, key);
keylist_update();
freersakey(key);
sfree(key);
ret[4] = SSH_AGENT_SUCCESS;
}
}
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;
if (msgend < p+4)
goto failure;
b.len = toint(GET_32BIT(p));
p += 4;
if (b.len < 0 || b.len > msgend - p)
goto failure;
b.blob = p;
p += b.len;
key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
if (!key)
goto failure;
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_FAILURE;
if (key) {
del234(ssh2keys, key);
keylist_update();
key->alg->freekey(key->data);
sfree(key);
ret[4] = SSH_AGENT_SUCCESS;
}
}
break;
case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
/*
* Remove all SSH-1 keys. Always returns success.
*/
{
struct RSAKey *rkey;
while ((rkey = index234(rsakeys, 0)) != NULL) {
del234(rsakeys, rkey);
freersakey(rkey);
sfree(rkey);
}
keylist_update();
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_SUCCESS;
}
break;
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
/*
* Remove all SSH-2 keys. Always returns success.
*/
{
struct ssh2_userkey *skey;
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;
}
break;
default:
failure:
/*
* Unrecognised message. Return SSH_AGENT_FAILURE.
*/
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_FAILURE;
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)
{
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)
{
return add234(rsakeys, rkey) != rkey;
}
int pageant_add_ssh2_key(struct ssh2_userkey *skey)
{
return add234(ssh2keys, skey) != skey;
}
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;
}