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