2 * Pageant: the PuTTY Authentication Agent.
19 #define IDI_MAINICON 200
20 #define IDI_TRAYICON 201
22 #define WM_XUSER (WM_USER + 0x2000)
23 #define WM_SYSTRAY (WM_XUSER + 6)
24 #define WM_SYSTRAY2 (WM_XUSER + 7)
26 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
29 * FIXME: maybe some day we can sort this out ...
31 #define AGENT_MAX_MSGLEN 8192
33 #define IDM_CLOSE 0x0010
34 #define IDM_VIEWKEYS 0x0020
35 #define IDM_ADDKEY 0x0030
36 #define IDM_HELP 0x0040
37 #define IDM_ABOUT 0x0050
39 #define APPNAME "Pageant"
43 static HINSTANCE instance;
44 static HWND main_hwnd;
47 static HMENU systray_menu;
48 static int already_running;
49 static int requested_help;
51 static char *help_path;
53 static tree234 *rsakeys, *ssh2keys;
55 static int has_security;
57 typedef DWORD(WINAPI * gsi_fn_t)
58 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
59 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
60 static gsi_fn_t getsecurityinfo;
64 * Exports from pageantc.c
66 void agent_query(void *in, int inlen, void **out, int *outlen);
67 int agent_exists(void);
72 static void *make_keylist1(int *length);
73 static void *make_keylist2(int *length);
74 static void *get_keylist1(void);
75 static void *get_keylist2(void);
78 * We need this to link with the RSA code, because rsaencrypt()
79 * pads its data with random bytes. Since we only use rsadecrypt()
80 * and the signing functions, which are deterministic, this should
83 * If it _is_ called, there is a _serious_ problem, because it
84 * won't generate true random numbers. So we must scream, panic,
85 * and exit immediately if that should happen.
89 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
91 /* this line can't be reached but it placates MSVC's warnings :-) */
96 * Blob structure for passing to the asymmetric SSH2 key compare
97 * function, prototyped here.
103 static int cmpkeys_ssh2_asymm(void *av, void *bv);
106 * This function is needed to link with the DES code. We need not
107 * have it do anything at all.
109 void logevent(char *msg)
113 #define GET_32BIT(cp) \
114 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
115 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
116 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
117 ((unsigned long)(unsigned char)(cp)[3]))
119 #define PUT_32BIT(cp, value) { \
120 (cp)[0] = (unsigned char)((value) >> 24); \
121 (cp)[1] = (unsigned char)((value) >> 16); \
122 (cp)[2] = (unsigned char)((value) >> 8); \
123 (cp)[3] = (unsigned char)(value); }
125 #define PASSPHRASE_MAXLEN 512
127 struct PassphraseProcStruct {
132 static tree234 *passphrases = NULL;
135 * After processing a list of filenames, we want to forget the
138 static void forget_passphrases(void)
140 while (count234(passphrases) > 0) {
141 char *pp = index234(passphrases, 0);
142 memset(pp, 0, strlen(pp));
143 delpos234(passphrases, 0);
149 * Dialog-box function for the Licence box.
151 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
152 WPARAM wParam, LPARAM lParam)
158 switch (LOWORD(wParam)) {
172 * Dialog-box function for the About box.
174 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
175 WPARAM wParam, LPARAM lParam)
179 SetDlgItemText(hwnd, 100, ver);
182 switch (LOWORD(wParam)) {
188 EnableWindow(hwnd, 0);
189 DialogBox(instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
190 EnableWindow(hwnd, 1);
191 SetActiveWindow(hwnd);
203 static HWND passphrase_box;
206 * Dialog-box function for the passphrase box.
208 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
209 WPARAM wParam, LPARAM lParam)
211 static char *passphrase = NULL;
212 struct PassphraseProcStruct *p;
216 passphrase_box = hwnd;
220 { /* centre the window */
224 hw = GetDesktopWindow();
225 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
227 (rs.right + rs.left + rd.left - rd.right) / 2,
228 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
229 rd.right - rd.left, rd.bottom - rd.top, TRUE);
232 SetForegroundWindow(hwnd);
233 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
234 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
235 p = (struct PassphraseProcStruct *) lParam;
236 passphrase = p->passphrase;
238 SetDlgItemText(hwnd, 101, p->comment);
240 SetDlgItemText(hwnd, 102, passphrase);
243 switch (LOWORD(wParam)) {
253 case 102: /* edit box */
254 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
255 GetDlgItemText(hwnd, 102, passphrase,
256 PASSPHRASE_MAXLEN - 1);
257 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
270 * Warn about the obsolescent key file format.
272 void old_keyfile_warning(void)
274 static const char mbtitle[] = "PuTTY Key File Warning";
275 static const char message[] =
276 "You are loading an SSH 2 private key which has an\n"
277 "old version of the file format. This means your key\n"
278 "file is not fully tamperproof. Future versions of\n"
279 "PuTTY may stop supporting this private key format,\n"
280 "so we recommend you convert your key to the new\n"
283 "You can perform this conversion by loading the key\n"
284 "into PuTTYgen and then saving it again.";
286 MessageBox(NULL, message, mbtitle, MB_OK);
290 * Update the visible key list.
292 static void keylist_update(void)
295 struct ssh2_userkey *skey;
299 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
300 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
301 char listentry[512], *p;
303 * Replace two spaces in the fingerprint with tabs, for
304 * nice alignment in the box.
306 strcpy(listentry, "ssh1\t");
307 p = listentry + strlen(listentry);
308 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
309 p = strchr(listentry, ' ');
312 p = strchr(listentry, ' ');
315 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
316 0, (LPARAM) listentry);
318 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
319 char listentry[512], *p;
322 * Replace two spaces in the fingerprint with tabs, for
323 * nice alignment in the box.
325 p = skey->alg->fingerprint(skey->data);
326 strncpy(listentry, p, sizeof(listentry));
327 p = strchr(listentry, ' ');
330 p = strchr(listentry, ' ');
333 len = strlen(listentry);
334 if (len < sizeof(listentry) - 2) {
335 listentry[len] = '\t';
336 strncpy(listentry + len + 1, skey->comment,
337 sizeof(listentry) - len - 1);
339 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
342 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
347 * This function loads a key from a file and adds it.
349 static void add_keyfile(char *filename)
351 char passphrase[PASSPHRASE_MAXLEN];
352 struct RSAKey *rkey = NULL;
353 struct ssh2_userkey *skey = NULL;
358 struct PassphraseProcStruct pps;
362 ver = keyfile_version(filename);
364 MessageBox(NULL, "Couldn't load private key.", APPNAME,
365 MB_OK | MB_ICONERROR);
370 * See if the key is already loaded (in the primary Pageant,
371 * which may or may not be us).
375 unsigned char *keylist, *p;
376 int i, nkeys, bloblen;
379 if (!rsakey_pubblob(filename, &blob, &bloblen)) {
380 MessageBox(NULL, "Couldn't load private key.", APPNAME,
381 MB_OK | MB_ICONERROR);
384 keylist = get_keylist1();
386 unsigned char *blob2;
387 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen);
389 MessageBox(NULL, "Couldn't load private key.", APPNAME,
390 MB_OK | MB_ICONERROR);
393 /* For our purposes we want the blob prefixed with its length */
394 blob2 = smalloc(bloblen+4);
395 PUT_32BIT(blob2, bloblen);
396 memcpy(blob2 + 4, blob, bloblen);
400 keylist = get_keylist2();
403 nkeys = GET_32BIT(keylist);
406 for (i = 0; i < nkeys; i++) {
407 if (!memcmp(blob, p, bloblen)) {
408 /* Key is already present; we can now leave. */
413 /* Now skip over public blob */
415 p += rsa_public_blob_len(p);
417 p += 4 + GET_32BIT(p);
418 /* Now skip over comment field */
419 p += 4 + GET_32BIT(p);
429 needs_pass = rsakey_encrypted(filename, &comment);
431 needs_pass = ssh2_userkey_encrypted(filename, &comment);
434 rkey = smalloc(sizeof(*rkey));
435 pps.passphrase = passphrase;
436 pps.comment = comment;
440 /* try all the remembered passphrases first */
441 char *pp = index234(passphrases, attempts);
443 strcpy(passphrase, pp);
447 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
448 NULL, PassphraseProc, (LPARAM) & pps);
449 passphrase_box = NULL;
455 return; /* operation cancelled */
461 ret = loadrsakey(filename, rkey, passphrase);
463 skey = ssh2_load_userkey(filename, passphrase);
464 if (skey == SSH2_WRONG_PASSPHRASE)
474 /* if they typed in an ok passphrase, remember it */
475 if(original_pass && ret) {
476 char *pp = dupstr(passphrase);
477 addpos234(passphrases, pp, 0);
483 MessageBox(NULL, "Couldn't load private key.", APPNAME,
484 MB_OK | MB_ICONERROR);
490 if (already_running) {
491 unsigned char *request, *response;
493 int reqlen, clen, resplen;
495 clen = strlen(rkey->comment);
497 reqlen = 4 + 1 + /* length, message type */
499 ssh1_bignum_length(rkey->modulus) +
500 ssh1_bignum_length(rkey->exponent) +
501 ssh1_bignum_length(rkey->private_exponent) +
502 ssh1_bignum_length(rkey->iqmp) +
503 ssh1_bignum_length(rkey->p) +
504 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
507 request = smalloc(reqlen);
509 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
511 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
513 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
514 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
516 ssh1_write_bignum(request + reqlen,
517 rkey->private_exponent);
518 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
519 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
520 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
521 PUT_32BIT(request + reqlen, clen);
522 memcpy(request + reqlen + 4, rkey->comment, clen);
524 PUT_32BIT(request, reqlen - 4);
526 agent_query(request, reqlen, &vresponse, &resplen);
527 response = vresponse;
528 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
529 MessageBox(NULL, "The already running Pageant "
530 "refused to add the key.", APPNAME,
531 MB_OK | MB_ICONERROR);
536 if (add234(rsakeys, rkey) != rkey)
537 sfree(rkey); /* already present, don't waste RAM */
540 if (already_running) {
541 unsigned char *request, *response;
543 int reqlen, alglen, clen, keybloblen, resplen;
544 alglen = strlen(skey->alg->name);
545 clen = strlen(skey->comment);
547 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
549 reqlen = 4 + 1 + /* length, message type */
550 4 + alglen + /* algorithm name */
551 keybloblen + /* key data */
552 4 + clen /* comment */
555 request = smalloc(reqlen);
557 request[4] = SSH2_AGENTC_ADD_IDENTITY;
559 PUT_32BIT(request + reqlen, alglen);
561 memcpy(request + reqlen, skey->alg->name, alglen);
563 reqlen += skey->alg->openssh_fmtkey(skey->data,
566 PUT_32BIT(request + reqlen, clen);
567 memcpy(request + reqlen + 4, skey->comment, clen);
568 PUT_32BIT(request, reqlen - 4);
571 agent_query(request, reqlen, &vresponse, &resplen);
572 response = vresponse;
573 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
574 MessageBox(NULL, "The already running Pageant"
575 "refused to add the key.", APPNAME,
576 MB_OK | MB_ICONERROR);
581 if (add234(ssh2keys, skey) != skey) {
582 skey->alg->freekey(skey->data);
583 sfree(skey); /* already present, don't waste RAM */
590 * Create an SSH1 key list in a malloc'ed buffer; return its
593 static void *make_keylist1(int *length)
597 unsigned char *blob, *p, *ret;
601 * Count up the number and length of keys we hold.
605 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
607 blob = rsa_public_blob(key, &bloblen);
610 len += 4 + strlen(key->comment);
613 /* Allocate the buffer. */
614 p = ret = smalloc(len);
615 if (length) *length = len;
619 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
620 blob = rsa_public_blob(key, &bloblen);
621 memcpy(p, blob, bloblen);
624 PUT_32BIT(p, strlen(key->comment));
625 memcpy(p + 4, key->comment, strlen(key->comment));
626 p += 4 + strlen(key->comment);
629 assert(p - ret == len);
634 * Create an SSH2 key list in a malloc'ed buffer; return its
637 static void *make_keylist2(int *length)
639 struct ssh2_userkey *key;
641 unsigned char *blob, *p, *ret;
645 * Count up the number and length of keys we hold.
649 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
651 len += 4; /* length field */
652 blob = key->alg->public_blob(key->data, &bloblen);
655 len += 4 + strlen(key->comment);
658 /* Allocate the buffer. */
659 p = ret = smalloc(len);
660 if (length) *length = len;
663 * Packet header is the obvious five bytes, plus four
664 * bytes for the key count.
668 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
669 blob = key->alg->public_blob(key->data, &bloblen);
670 PUT_32BIT(p, bloblen);
672 memcpy(p, blob, bloblen);
675 PUT_32BIT(p, strlen(key->comment));
676 memcpy(p + 4, key->comment, strlen(key->comment));
677 p += 4 + strlen(key->comment);
680 assert(p - ret == len);
685 * Acquire a keylist1 from the primary Pageant; this means either
686 * calling make_keylist1 (if that's us) or sending a message to the
687 * primary Pageant (if it's not).
689 static void *get_keylist1(void)
693 if (already_running) {
694 unsigned char request[5], *response;
697 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
698 PUT_32BIT(request, 4);
700 agent_query(request, 5, &vresponse, &resplen);
701 response = vresponse;
702 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
705 ret = smalloc(resplen-5);
706 memcpy(ret, response+5, resplen-5);
709 ret = make_keylist1(NULL);
715 * Acquire a keylist2 from the primary Pageant; this means either
716 * calling make_keylist2 (if that's us) or sending a message to the
717 * primary Pageant (if it's not).
719 static void *get_keylist2(void)
723 if (already_running) {
724 unsigned char request[5], *response;
728 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
729 PUT_32BIT(request, 4);
731 agent_query(request, 5, &vresponse, &resplen);
732 response = vresponse;
733 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
736 ret = smalloc(resplen-5);
737 memcpy(ret, response+5, resplen-5);
740 ret = make_keylist2(NULL);
746 * This is the main agent function that answers messages.
748 static void answer_msg(void *msg)
750 unsigned char *p = msg;
751 unsigned char *ret = msg;
755 * Get the message type.
761 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
763 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
769 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
770 keylist = make_keylist1(&len);
771 if (len + 5 > AGENT_MAX_MSGLEN) {
775 PUT_32BIT(ret, len + 1);
776 memcpy(ret + 5, keylist, len);
780 case SSH2_AGENTC_REQUEST_IDENTITIES:
782 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
788 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
789 keylist = make_keylist2(&len);
790 if (len + 5 > AGENT_MAX_MSGLEN) {
794 PUT_32BIT(ret, len + 1);
795 memcpy(ret + 5, keylist, len);
799 case SSH1_AGENTC_RSA_CHALLENGE:
801 * Reply with either SSH1_AGENT_RSA_RESPONSE or
802 * SSH_AGENT_FAILURE, depending on whether we have that key
806 struct RSAKey reqkey, *key;
807 Bignum challenge, response;
808 unsigned char response_source[48], response_md5[16];
809 struct MD5Context md5c;
813 p += ssh1_read_bignum(p, &reqkey.exponent);
814 p += ssh1_read_bignum(p, &reqkey.modulus);
815 p += ssh1_read_bignum(p, &challenge);
816 memcpy(response_source + 32, p, 16);
818 if (GET_32BIT(p) != 1 ||
819 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
820 freebn(reqkey.exponent);
821 freebn(reqkey.modulus);
825 response = rsadecrypt(challenge, key);
826 for (i = 0; i < 32; i++)
827 response_source[i] = bignum_byte(response, 31 - i);
830 MD5Update(&md5c, response_source, 48);
831 MD5Final(response_md5, &md5c);
832 memset(response_source, 0, 48); /* burn the evidence */
833 freebn(response); /* and that evidence */
834 freebn(challenge); /* yes, and that evidence */
835 freebn(reqkey.exponent); /* and free some memory ... */
836 freebn(reqkey.modulus); /* ... while we're at it. */
839 * Packet is the obvious five byte header, plus sixteen
843 PUT_32BIT(ret, len - 4);
844 ret[4] = SSH1_AGENT_RSA_RESPONSE;
845 memcpy(ret + 5, response_md5, 16);
848 case SSH2_AGENTC_SIGN_REQUEST:
850 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
851 * SSH_AGENT_FAILURE, depending on whether we have that key
855 struct ssh2_userkey *key;
857 unsigned char *data, *signature;
858 int datalen, siglen, len;
860 b.len = GET_32BIT(p);
864 datalen = GET_32BIT(p);
867 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
870 signature = key->alg->sign(key->data, data, datalen, &siglen);
871 len = 5 + 4 + siglen;
872 PUT_32BIT(ret, len - 4);
873 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
874 PUT_32BIT(ret + 5, siglen);
875 memcpy(ret + 5 + 4, signature, siglen);
879 case SSH1_AGENTC_ADD_RSA_IDENTITY:
881 * Add to the list and return SSH_AGENT_SUCCESS, or
882 * SSH_AGENT_FAILURE if the key was malformed.
888 key = smalloc(sizeof(struct RSAKey));
889 memset(key, 0, sizeof(struct RSAKey));
890 p += makekey(p, key, NULL, 1);
891 p += makeprivate(p, key);
892 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
893 p += ssh1_read_bignum(p, &key->p); /* p */
894 p += ssh1_read_bignum(p, &key->q); /* q */
895 commentlen = GET_32BIT(p);
896 comment = smalloc(commentlen+1);
898 memcpy(comment, p + 4, commentlen);
899 comment[commentlen] = '\0';
900 key->comment = comment;
903 ret[4] = SSH_AGENT_FAILURE;
904 if (add234(rsakeys, key) == key) {
906 ret[4] = SSH_AGENT_SUCCESS;
913 case SSH2_AGENTC_ADD_IDENTITY:
915 * Add to the list and return SSH_AGENT_SUCCESS, or
916 * SSH_AGENT_FAILURE if the key was malformed.
919 struct ssh2_userkey *key;
924 key = smalloc(sizeof(struct ssh2_userkey));
926 alglen = GET_32BIT(p);
930 /* Add further algorithm names here. */
931 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
933 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
941 GET_32BIT((unsigned char *) msg) - (p -
942 (unsigned char *) msg -
944 key->data = key->alg->openssh_createkey(&p, &bloblen);
949 commlen = GET_32BIT(p);
952 comment = smalloc(commlen + 1);
954 memcpy(comment, p, commlen);
955 comment[commlen] = '\0';
957 key->comment = comment;
960 ret[4] = SSH_AGENT_FAILURE;
961 if (add234(ssh2keys, key) == key) {
963 ret[4] = SSH_AGENT_SUCCESS;
965 key->alg->freekey(key->data);
971 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
973 * Remove from the list and return SSH_AGENT_SUCCESS, or
974 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
978 struct RSAKey reqkey, *key;
980 p += makekey(p, &reqkey, NULL, 0);
981 key = find234(rsakeys, &reqkey, NULL);
982 freebn(reqkey.exponent);
983 freebn(reqkey.modulus);
985 ret[4] = SSH_AGENT_FAILURE;
987 del234(rsakeys, key);
991 ret[4] = SSH_AGENT_SUCCESS;
995 case SSH2_AGENTC_REMOVE_IDENTITY:
997 * Remove from the list and return SSH_AGENT_SUCCESS, or
998 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1002 struct ssh2_userkey *key;
1005 b.len = GET_32BIT(p);
1009 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1014 ret[4] = SSH_AGENT_FAILURE;
1016 del234(ssh2keys, key);
1018 key->alg->freekey(key->data);
1020 ret[4] = SSH_AGENT_SUCCESS;
1024 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1026 * Remove all SSH1 keys. Always returns success.
1029 struct RSAKey *rkey;
1031 while ((rkey = index234(rsakeys, 0)) != NULL) {
1032 del234(rsakeys, rkey);
1039 ret[4] = SSH_AGENT_SUCCESS;
1042 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1044 * Remove all SSH2 keys. Always returns success.
1047 struct ssh2_userkey *skey;
1049 while ((skey = index234(ssh2keys, 0)) != NULL) {
1050 del234(ssh2keys, skey);
1051 skey->alg->freekey(skey->data);
1057 ret[4] = SSH_AGENT_SUCCESS;
1063 * Unrecognised message. Return SSH_AGENT_FAILURE.
1066 ret[4] = SSH_AGENT_FAILURE;
1072 * Key comparison function for the 2-3-4 tree of RSA keys.
1074 static int cmpkeys_rsa(void *av, void *bv)
1076 struct RSAKey *a = (struct RSAKey *) av;
1077 struct RSAKey *b = (struct RSAKey *) bv;
1084 * Compare by length of moduli.
1086 alen = bignum_bitcount(am);
1087 blen = bignum_bitcount(bm);
1090 else if (alen < blen)
1093 * Now compare by moduli themselves.
1095 alen = (alen + 7) / 8; /* byte count */
1096 while (alen-- > 0) {
1098 abyte = bignum_byte(am, alen);
1099 bbyte = bignum_byte(bm, alen);
1102 else if (abyte < bbyte)
1112 * Key comparison function for the 2-3-4 tree of SSH2 keys.
1114 static int cmpkeys_ssh2(void *av, void *bv)
1116 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1117 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1120 unsigned char *ablob, *bblob;
1124 * Compare purely by public blob.
1126 ablob = a->alg->public_blob(a->data, &alen);
1127 bblob = b->alg->public_blob(b->data, &blen);
1130 for (i = 0; i < alen && i < blen; i++) {
1131 if (ablob[i] < bblob[i]) {
1134 } else if (ablob[i] > bblob[i]) {
1139 if (c == 0 && i < alen)
1140 c = +1; /* a is longer */
1141 if (c == 0 && i < blen)
1142 c = -1; /* a is longer */
1151 * Key comparison function for looking up a blob in the 2-3-4 tree
1154 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1156 struct blob *a = (struct blob *) av;
1157 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1160 unsigned char *ablob, *bblob;
1164 * Compare purely by public blob.
1168 bblob = b->alg->public_blob(b->data, &blen);
1171 for (i = 0; i < alen && i < blen; i++) {
1172 if (ablob[i] < bblob[i]) {
1175 } else if (ablob[i] > bblob[i]) {
1180 if (c == 0 && i < alen)
1181 c = +1; /* a is longer */
1182 if (c == 0 && i < blen)
1183 c = -1; /* a is longer */
1191 * Prompt for a key file to add, and add it.
1193 static void prompt_add_keyfile(void)
1196 char filename[FILENAME_MAX];
1197 char *filelist = smalloc(8192);
1201 memset(&of, 0, sizeof(of));
1202 #ifdef OPENFILENAME_SIZE_VERSION_400
1203 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1205 of.lStructSize = sizeof(of);
1207 of.hwndOwner = main_hwnd;
1208 of.lpstrFilter = "All Files\0*\0\0\0";
1209 of.lpstrCustomFilter = NULL;
1210 of.nFilterIndex = 1;
1211 of.lpstrFile = filelist;
1213 of.nMaxFile = FILENAME_MAX;
1214 of.lpstrFileTitle = NULL;
1215 of.lpstrInitialDir = NULL;
1216 of.lpstrTitle = "Select Private Key File";
1217 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1218 if (GetOpenFileName(&of)) {
1219 if(strlen(filelist) > of.nFileOffset)
1220 /* Only one filename returned? */
1221 add_keyfile(filelist);
1223 /* we are returned a bunch of strings, end to
1224 * end. first string is the directory, the
1225 * rest the filenames. terminated with an
1228 filewalker = filelist;
1229 dirlen = strlen(filewalker);
1230 if(dirlen > FILENAME_MAX - 8) return;
1231 memcpy(filename, filewalker, dirlen);
1233 filewalker += dirlen + 1;
1234 filename[dirlen++] = '\\';
1236 /* then go over names one by one */
1238 n = strlen(filewalker) + 1;
1239 /* end of the list */
1242 /* too big, shouldn't happen */
1243 if(n + dirlen > FILENAME_MAX)
1246 memcpy(filename + dirlen, filewalker, n);
1249 add_keyfile(filename);
1254 forget_passphrases();
1260 * Dialog-box function for the key list box.
1262 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1263 WPARAM wParam, LPARAM lParam)
1265 struct RSAKey *rkey;
1266 struct ssh2_userkey *skey;
1271 * Centre the window.
1273 { /* centre the window */
1277 hw = GetDesktopWindow();
1278 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1280 (rs.right + rs.left + rd.left - rd.right) / 2,
1281 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1282 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1286 SetWindowLong(hwnd, GWL_EXSTYLE,
1287 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1289 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1291 DestroyWindow(item);
1293 requested_help = FALSE;
1297 static int tabs[] = { 35, 60, 210 };
1298 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1299 sizeof(tabs) / sizeof(*tabs),
1305 switch (LOWORD(wParam)) {
1309 DestroyWindow(hwnd);
1311 case 101: /* add key */
1312 if (HIWORD(wParam) == BN_CLICKED ||
1313 HIWORD(wParam) == BN_DOUBLECLICKED) {
1314 if (passphrase_box) {
1315 MessageBeep(MB_ICONERROR);
1316 SetForegroundWindow(passphrase_box);
1319 prompt_add_keyfile();
1322 case 102: /* remove key */
1323 if (HIWORD(wParam) == BN_CLICKED ||
1324 HIWORD(wParam) == BN_DOUBLECLICKED) {
1329 /* our counter within the array of selected items */
1332 /* get the number of items selected in the list */
1334 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1336 /* none selected? that was silly */
1337 if (numSelected == 0) {
1342 /* get item indices in an array */
1343 selectedArray = smalloc(numSelected * sizeof(int));
1344 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1345 numSelected, (WPARAM)selectedArray);
1347 itemNum = numSelected - 1;
1348 rCount = count234(rsakeys);
1349 sCount = count234(ssh2keys);
1351 /* go through the non-rsakeys until we've covered them all,
1352 * and/or we're out of selected items to check. note that
1353 * we go *backwards*, to avoid complications from deleting
1354 * things hence altering the offset of subsequent items
1356 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1357 skey = index234(ssh2keys, i);
1359 if (selectedArray[itemNum] == rCount + i) {
1360 del234(ssh2keys, skey);
1361 skey->alg->freekey(skey->data);
1367 /* do the same for the rsa keys */
1368 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1369 rkey = index234(rsakeys, i);
1371 if(selectedArray[itemNum] == i) {
1372 del234(rsakeys, rkey);
1379 sfree(selectedArray);
1383 case 103: /* help */
1384 if (HIWORD(wParam) == BN_CLICKED ||
1385 HIWORD(wParam) == BN_DOUBLECLICKED) {
1387 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1388 (DWORD)"JI(`',`pageant.general')");
1389 requested_help = TRUE;
1397 int id = ((LPHELPINFO)lParam)->iCtrlId;
1400 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1401 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1402 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1405 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1406 requested_help = TRUE;
1414 DestroyWindow(hwnd);
1420 /* Set up a system tray icon */
1421 static BOOL AddTrayIcon(HWND hwnd)
1424 NOTIFYICONDATA tnid;
1427 #ifdef NIM_SETVERSION
1429 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1432 tnid.cbSize = sizeof(NOTIFYICONDATA);
1434 tnid.uID = 1; /* unique within this systray use */
1435 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1436 tnid.uCallbackMessage = WM_SYSTRAY;
1437 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1438 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1440 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1442 if (hicon) DestroyIcon(hicon);
1447 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1448 WPARAM wParam, LPARAM lParam)
1451 static int menuinprogress;
1452 static UINT msgTaskbarCreated = 0;
1456 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1459 if (message==msgTaskbarCreated) {
1461 * Explorer has been restarted, so the tray icon will
1469 if (lParam == WM_RBUTTONUP) {
1471 GetCursorPos(&cursorpos);
1472 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1473 } else if (lParam == WM_LBUTTONDBLCLK) {
1474 /* Equivalent to IDM_VIEWKEYS. */
1475 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1479 if (!menuinprogress) {
1481 SetForegroundWindow(hwnd);
1482 ret = TrackPopupMenu(systray_menu,
1483 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1485 wParam, lParam, 0, hwnd, NULL);
1491 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1494 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1495 SendMessage(hwnd, WM_CLOSE, 0, 0);
1499 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1501 ShowWindow(keylist, SW_SHOWNORMAL);
1503 * Sometimes the window comes up minimised / hidden
1504 * for no obvious reason. Prevent this.
1506 SetForegroundWindow(keylist);
1507 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1508 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1512 if (passphrase_box) {
1513 MessageBeep(MB_ICONERROR);
1514 SetForegroundWindow(passphrase_box);
1517 prompt_add_keyfile();
1521 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1523 ShowWindow(aboutbox, SW_SHOWNORMAL);
1525 * Sometimes the window comes up minimised / hidden
1526 * for no obvious reason. Prevent this.
1528 SetForegroundWindow(aboutbox);
1529 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1530 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1535 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1536 (DWORD)"JI(`',`pageant.general')");
1537 requested_help = TRUE;
1543 if (requested_help) {
1544 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1545 requested_help = FALSE;
1551 COPYDATASTRUCT *cds;
1557 PSID mapowner, procowner;
1558 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1562 cds = (COPYDATASTRUCT *) lParam;
1563 if (cds->dwData != AGENT_COPYDATA_ID)
1564 return 0; /* not our message, mate */
1565 mapname = (char *) cds->lpData;
1566 if (mapname[cds->cbData - 1] != '\0')
1567 return 0; /* failure to be ASCIZ! */
1569 debug(("mapname is :%s:\n", mapname));
1571 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1573 debug(("filemap is %p\n", filemap));
1575 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1579 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1580 GetCurrentProcessId())) ==
1583 debug(("couldn't get handle for process\n"));
1587 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1588 OWNER_SECURITY_INFORMATION,
1589 &procowner, NULL, NULL, NULL,
1590 &psd2) != ERROR_SUCCESS) {
1592 debug(("couldn't get owner info for process\n"));
1595 return 0; /* unable to get security info */
1598 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1599 OWNER_SECURITY_INFORMATION,
1600 &mapowner, NULL, NULL, NULL,
1601 &psd1) != ERROR_SUCCESS)) {
1604 ("couldn't get owner info for filemap: %d\n",
1610 debug(("got security stuff\n"));
1612 if (!EqualSid(mapowner, procowner))
1613 return 0; /* security ID mismatch! */
1615 debug(("security stuff matched\n"));
1621 debug(("security APIs not present\n"));
1625 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1627 debug(("p is %p\n", p));
1630 for (i = 0; i < 5; i++)
1633 ((unsigned char *) p)[i]));}
1639 CloseHandle(filemap);
1644 return DefWindowProc(hwnd, message, wParam, lParam);
1648 * Fork and Exec the command in cmdline. [DBW]
1650 void spawn_cmd(char *cmdline, char * args, int show)
1652 if (ShellExecute(NULL, _T("open"), cmdline,
1653 args, NULL, show) <= (HINSTANCE) 32) {
1655 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
1656 (int)GetLastError());
1657 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1661 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1667 char *command = NULL;
1671 * Determine whether we're an NT system (should have security
1672 * APIs) or a non-NT system (don't do security).
1674 memset(&osi, 0, sizeof(OSVERSIONINFO));
1675 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1676 if (GetVersionEx(&osi) && osi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1677 has_security = TRUE;
1679 has_security = FALSE;
1684 * Attempt to get the security API we need.
1686 advapi = LoadLibrary("ADVAPI32.DLL");
1688 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1689 if (!getsecurityinfo) {
1691 "Unable to access security APIs. Pageant will\n"
1692 "not run, in case it causes a security breach.",
1693 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1698 "This program has been compiled for Win9X and will\n"
1699 "not run on NT, in case it causes a security breach.",
1700 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1709 * See if we can find our Help file.
1712 char b[2048], *p, *q, *r;
1714 GetModuleFileName(NULL, b, sizeof(b) - 1);
1716 p = strrchr(b, '\\');
1717 if (p && p >= r) r = p+1;
1718 q = strrchr(b, ':');
1719 if (q && q >= r) r = q+1;
1720 strcpy(r, "putty.hlp");
1721 if ( (fp = fopen(b, "r")) != NULL) {
1722 help_path = dupstr(b);
1729 * Find out if Pageant is already running.
1731 already_running = FALSE;
1733 already_running = TRUE;
1738 wndclass.lpfnWndProc = WndProc;
1739 wndclass.cbClsExtra = 0;
1740 wndclass.cbWndExtra = 0;
1741 wndclass.hInstance = inst;
1742 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1743 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1744 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1745 wndclass.lpszMenuName = NULL;
1746 wndclass.lpszClassName = APPNAME;
1748 RegisterClass(&wndclass);
1751 main_hwnd = keylist = NULL;
1753 main_hwnd = CreateWindow(APPNAME, APPNAME,
1754 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1755 CW_USEDEFAULT, CW_USEDEFAULT,
1756 100, 100, NULL, NULL, inst, NULL);
1758 /* Set up a system tray icon */
1759 AddTrayIcon(main_hwnd);
1761 systray_menu = CreatePopupMenu();
1762 /* accelerators used: vkxa */
1763 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1765 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1766 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1768 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1769 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1770 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1771 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1773 ShowWindow(main_hwnd, SW_HIDE);
1776 * Initialise storage for RSA keys.
1778 rsakeys = newtree234(cmpkeys_rsa);
1779 ssh2keys = newtree234(cmpkeys_ssh2);
1784 * Initialise storage for short-term passphrase cache.
1786 passphrases = newtree234(NULL);
1789 * Process the command line and add keys as listed on it.
1790 * If we already determined that we need to spawn a program from above we
1791 * need to ignore the first two arguments. [DBW]
1798 while (*p && isspace(*p))
1800 if (*p && !isspace(*p)) {
1801 char *q = p, *pp = p;
1802 while (*p && (inquotes || !isspace(*p))) {
1804 inquotes = !inquotes;
1815 if (!strcmp(q, "-c")) {
1817 * If we see `-c', then the rest of the
1818 * command line should be treated as a
1819 * command to be spawned.
1821 while (*p && isspace(*p))
1834 * Forget any passphrase that we retained while going over
1835 * command line keyfiles.
1837 forget_passphrases();
1841 if (command[0] == '"')
1842 args = strchr(++command, '"');
1844 args = strchr(command, ' ');
1847 while(*args && isspace(*args)) args++;
1849 spawn_cmd(command, args, show);
1853 * If Pageant was already running, we leave now. If we haven't
1854 * even taken any auxiliary action (spawned a command or added
1857 if (already_running) {
1858 if (!command && !added_keys) {
1859 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1860 MB_ICONERROR | MB_OK);
1863 FreeLibrary(advapi);
1868 * Main message loop.
1870 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1871 TranslateMessage(&msg);
1872 DispatchMessage(&msg);
1875 /* Clean up the system tray icon */
1877 NOTIFYICONDATA tnid;
1879 tnid.cbSize = sizeof(NOTIFYICONDATA);
1880 tnid.hWnd = main_hwnd;
1883 Shell_NotifyIcon(NIM_DELETE, &tnid);
1885 DestroyMenu(systray_menu);
1889 FreeLibrary(advapi);