1b303e2ae49403b85d6c4714e6f4579933e2b55a
[u/mdw/putty] / pageant.c
1 /*
2 * Pageant: the PuTTY Authentication Agent.
3 */
4
5 #include <windows.h>
6 #ifndef NO_SECURITY
7 #include <aclapi.h>
8 #endif
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <ctype.h>
12 #include <tchar.h>
13
14 #include "ssh.h"
15 #include "tree234.h"
16
17 #define IDI_MAINICON 200
18 #define IDI_TRAYICON 201
19
20 #define WM_XUSER (WM_USER + 0x2000)
21 #define WM_SYSTRAY (WM_XUSER + 6)
22 #define WM_SYSTRAY2 (WM_XUSER + 7)
23
24 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
25
26 /*
27 * FIXME: maybe some day we can sort this out ...
28 */
29 #define AGENT_MAX_MSGLEN 8192
30
31 #define IDM_CLOSE 0x0010
32 #define IDM_VIEWKEYS 0x0020
33 #define IDM_ADDKEY 0x0030
34 #define IDM_ABOUT 0x0040
35
36 #define APPNAME "Pageant"
37
38 extern char ver[];
39
40 static HINSTANCE instance;
41 static HWND hwnd;
42 static HWND keylist;
43 static HWND aboutbox;
44 static HMENU systray_menu;
45 static int already_running;
46
47 static tree234 *rsakeys, *ssh2keys;
48
49 static int has_security;
50 #ifndef NO_SECURITY
51 typedef DWORD (WINAPI *gsi_fn_t)
52 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
53 PSID *, PSID *, PACL *, PACL *,
54 PSECURITY_DESCRIPTOR *);
55 static gsi_fn_t getsecurityinfo;
56 #endif
57
58 /*
59 * We need this to link with the RSA code, because rsaencrypt()
60 * pads its data with random bytes. Since we only use rsadecrypt()
61 * and the signing functions, which are deterministic, this should
62 * never be called.
63 *
64 * If it _is_ called, there is a _serious_ problem, because it
65 * won't generate true random numbers. So we must scream, panic,
66 * and exit immediately if that should happen.
67 */
68 int random_byte(void) {
69 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
70 exit(0);
71 }
72
73 /*
74 * Blob structure for passing to the asymmetric SSH2 key compare
75 * function, prototyped here.
76 */
77 struct blob {
78 unsigned char *blob;
79 int len;
80 };
81 static int cmpkeys_ssh2_asymm(void *av, void *bv);
82
83 /*
84 * This function is needed to link with the DES code. We need not
85 * have it do anything at all.
86 */
87 void logevent(char *msg) {
88 }
89
90 #define GET_32BIT(cp) \
91 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
92 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
93 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
94 ((unsigned long)(unsigned char)(cp)[3]))
95
96 #define PUT_32BIT(cp, value) { \
97 (cp)[0] = (unsigned char)((value) >> 24); \
98 (cp)[1] = (unsigned char)((value) >> 16); \
99 (cp)[2] = (unsigned char)((value) >> 8); \
100 (cp)[3] = (unsigned char)(value); }
101
102 #define PASSPHRASE_MAXLEN 512
103
104 struct PassphraseProcStruct {
105 char *passphrase;
106 char *comment;
107 };
108
109 /*
110 * Dialog-box function for the Licence box.
111 */
112 static int CALLBACK LicenceProc (HWND hwnd, UINT msg,
113 WPARAM wParam, LPARAM lParam) {
114 switch (msg) {
115 case WM_INITDIALOG:
116 return 1;
117 case WM_COMMAND:
118 switch (LOWORD(wParam)) {
119 case IDOK:
120 EndDialog(hwnd, 1);
121 return 0;
122 }
123 return 0;
124 case WM_CLOSE:
125 EndDialog(hwnd, 1);
126 return 0;
127 }
128 return 0;
129 }
130
131 /*
132 * Dialog-box function for the About box.
133 */
134 static int CALLBACK AboutProc (HWND hwnd, UINT msg,
135 WPARAM wParam, LPARAM lParam) {
136 switch (msg) {
137 case WM_INITDIALOG:
138 SetDlgItemText (hwnd, 100, ver);
139 return 1;
140 case WM_COMMAND:
141 switch (LOWORD(wParam)) {
142 case IDOK:
143 aboutbox = NULL;
144 DestroyWindow (hwnd);
145 return 0;
146 case 101:
147 EnableWindow(hwnd, 0);
148 DialogBox (instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
149 EnableWindow(hwnd, 1);
150 SetActiveWindow(hwnd);
151 return 0;
152 }
153 return 0;
154 case WM_CLOSE:
155 aboutbox = NULL;
156 DestroyWindow (hwnd);
157 return 0;
158 }
159 return 0;
160 }
161
162 /*
163 * Dialog-box function for the passphrase box.
164 */
165 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
166 WPARAM wParam, LPARAM lParam) {
167 static char *passphrase;
168 struct PassphraseProcStruct *p;
169
170 switch (msg) {
171 case WM_INITDIALOG:
172 /*
173 * Centre the window.
174 */
175 { /* centre the window */
176 RECT rs, rd;
177 HWND hw;
178
179 hw = GetDesktopWindow();
180 if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
181 MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
182 (rs.bottom + rs.top + rd.top - rd.bottom)/2,
183 rd.right-rd.left, rd.bottom-rd.top, TRUE);
184 }
185
186 SetForegroundWindow(hwnd);
187 SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
188 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
189 p = (struct PassphraseProcStruct *)lParam;
190 passphrase = p->passphrase;
191 if (p->comment)
192 SetDlgItemText(hwnd, 101, p->comment);
193 *passphrase = 0;
194 return 0;
195 case WM_COMMAND:
196 switch (LOWORD(wParam)) {
197 case IDOK:
198 if (*passphrase)
199 EndDialog (hwnd, 1);
200 else
201 MessageBeep (0);
202 return 0;
203 case IDCANCEL:
204 EndDialog (hwnd, 0);
205 return 0;
206 case 102: /* edit box */
207 if (HIWORD(wParam) == EN_CHANGE) {
208 GetDlgItemText (hwnd, 102, passphrase, PASSPHRASE_MAXLEN-1);
209 passphrase[PASSPHRASE_MAXLEN-1] = '\0';
210 }
211 return 0;
212 }
213 return 0;
214 case WM_CLOSE:
215 EndDialog (hwnd, 0);
216 return 0;
217 }
218 return 0;
219 }
220
221 /*
222 * Update the visible key list.
223 */
224 static void keylist_update(void) {
225 struct RSAKey *rkey;
226 struct ssh2_userkey *skey;
227 int i;
228
229 if (keylist) {
230 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
231 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
232 char listentry[512], *p;
233 /*
234 * Replace two spaces in the fingerprint with tabs, for
235 * nice alignment in the box.
236 */
237 strcpy(listentry, "ssh1\t");
238 p = listentry+strlen(listentry);
239 rsa_fingerprint(p, sizeof(listentry)-(p-listentry), rkey);
240 p = strchr(listentry, ' '); if (p) *p = '\t';
241 p = strchr(listentry, ' '); if (p) *p = '\t';
242 SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
243 0, (LPARAM)listentry);
244 }
245 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
246 char listentry[512], *p;
247 int len;
248 /*
249 * Replace two spaces in the fingerprint with tabs, for
250 * nice alignment in the box.
251 */
252 p = skey->alg->fingerprint(skey->data);
253 strncpy(listentry, p, sizeof(listentry));
254 p = strchr(listentry, ' '); if (p) *p = '\t';
255 p = strchr(listentry, ' '); if (p) *p = '\t';
256 len = strlen(listentry);
257 if (len < sizeof(listentry)-2) {
258 listentry[len] = '\t';
259 strncpy(listentry+len+1, skey->comment, sizeof(listentry)-len-1);
260 }
261 SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
262 0, (LPARAM)listentry);
263 }
264 SendDlgItemMessage (keylist, 100, LB_SETCURSEL, (WPARAM) -1, 0);
265 }
266 }
267
268 /*
269 * This function loads a key from a file and adds it.
270 */
271 static void add_keyfile(char *filename) {
272 char passphrase[PASSPHRASE_MAXLEN];
273 struct RSAKey *rkey;
274 struct ssh2_userkey *skey;
275 int needs_pass;
276 int ret;
277 int attempts;
278 char *comment;
279 struct PassphraseProcStruct pps;
280 int ver;
281
282 ver = keyfile_version(filename);
283 if (ver == 0) {
284 MessageBox(NULL, "Couldn't load private key.", APPNAME,
285 MB_OK | MB_ICONERROR);
286 return;
287 }
288
289 if (ver == 1)
290 needs_pass = rsakey_encrypted(filename, &comment);
291 else
292 needs_pass = ssh2_userkey_encrypted(filename, &comment);
293 attempts = 0;
294 if (ver == 1)
295 rkey = smalloc(sizeof(*rkey));
296 pps.passphrase = passphrase;
297 pps.comment = comment;
298 do {
299 if (needs_pass) {
300 int dlgret;
301 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
302 NULL, PassphraseProc,
303 (LPARAM)&pps);
304 if (!dlgret) {
305 if (comment) sfree(comment);
306 if (ver == 1)
307 sfree(rkey);
308 return; /* operation cancelled */
309 }
310 } else
311 *passphrase = '\0';
312 if (ver == 1)
313 ret = loadrsakey(filename, rkey, passphrase);
314 else {
315 skey = ssh2_load_userkey(filename, passphrase);
316 if (skey == SSH2_WRONG_PASSPHRASE)
317 ret = -1;
318 else if (!skey)
319 ret = 0;
320 else
321 ret = 1;
322 }
323 attempts++;
324 } while (ret == -1);
325 if (comment) sfree(comment);
326 if (ret == 0) {
327 MessageBox(NULL, "Couldn't load private key.", APPNAME,
328 MB_OK | MB_ICONERROR);
329 if (ver == 1)
330 sfree(rkey);
331 return;
332 }
333 if (ver == 1) {
334 if (already_running) {
335 unsigned char *request, *response;
336 int reqlen, clen, resplen;
337
338 clen = strlen(rkey->comment);
339
340 reqlen = 4 + 1 + /* length, message type */
341 4 + /* bit count */
342 ssh1_bignum_length(rkey->modulus) +
343 ssh1_bignum_length(rkey->exponent) +
344 ssh1_bignum_length(rkey->private_exponent) +
345 ssh1_bignum_length(rkey->iqmp) +
346 ssh1_bignum_length(rkey->p) +
347 ssh1_bignum_length(rkey->q) +
348 4 + clen /* comment */
349 ;
350
351 request = smalloc(reqlen);
352
353 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
354 reqlen = 5;
355 PUT_32BIT(request+reqlen, bignum_bitcount(rkey->modulus));
356 reqlen += 4;
357 reqlen += ssh1_write_bignum(request+reqlen, rkey->modulus);
358 reqlen += ssh1_write_bignum(request+reqlen, rkey->exponent);
359 reqlen += ssh1_write_bignum(request+reqlen, rkey->private_exponent);
360 reqlen += ssh1_write_bignum(request+reqlen, rkey->iqmp);
361 reqlen += ssh1_write_bignum(request+reqlen, rkey->p);
362 reqlen += ssh1_write_bignum(request+reqlen, rkey->q);
363 PUT_32BIT(request+reqlen, clen);
364 memcpy(request+reqlen+4, rkey->comment, clen);
365 reqlen += 4+clen;
366 PUT_32BIT(request, reqlen-4);
367
368 agent_query(request, reqlen, &response, &resplen);
369 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
370 MessageBox(NULL, "The already running Pageant "
371 "refused to add the key.", APPNAME,
372 MB_OK | MB_ICONERROR);
373 } else {
374 if (add234(rsakeys, rkey) != rkey)
375 sfree(rkey); /* already present, don't waste RAM */
376 }
377 } else {
378 if (already_running) {
379 unsigned char *request, *response;
380 int reqlen, alglen, clen, keybloblen, resplen;
381 alglen = strlen(skey->alg->name);
382 clen = strlen(skey->comment);
383
384 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
385
386 reqlen = 4 + 1 + /* length, message type */
387 4 + alglen + /* algorithm name */
388 keybloblen + /* key data */
389 4 + clen /* comment */
390 ;
391
392 request = smalloc(reqlen);
393
394 request[4] = SSH2_AGENTC_ADD_IDENTITY;
395 reqlen = 5;
396 PUT_32BIT(request+reqlen, alglen);
397 reqlen += 4;
398 memcpy(request+reqlen, skey->alg->name, alglen);
399 reqlen += alglen;
400 reqlen += skey->alg->openssh_fmtkey(skey->data,
401 request+reqlen, keybloblen);
402 PUT_32BIT(request+reqlen, clen);
403 memcpy(request+reqlen+4, skey->comment, clen);
404 PUT_32BIT(request, reqlen-4);
405 reqlen += clen+4;
406
407 agent_query(request, reqlen, &response, &resplen);
408 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
409 MessageBox(NULL, "The already running Pageant"
410 "refused to add the key.", APPNAME,
411 MB_OK | MB_ICONERROR);
412 } else {
413 if (add234(ssh2keys, skey) != skey) {
414 skey->alg->freekey(skey->data);
415 sfree(skey); /* already present, don't waste RAM */
416 }
417 }
418 }
419 }
420
421 /*
422 * This is the main agent function that answers messages.
423 */
424 static void answer_msg(void *msg) {
425 unsigned char *p = msg;
426 unsigned char *ret = msg;
427 int type;
428
429 /*
430 * Get the message type.
431 */
432 type = p[4];
433
434 p += 5;
435 switch (type) {
436 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
437 /*
438 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
439 */
440 {
441 struct RSAKey *key;
442 int len, nkeys;
443 int i;
444
445 /*
446 * Count up the number and length of keys we hold.
447 */
448 len = nkeys = 0;
449 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
450 nkeys++;
451 len += 4; /* length field */
452 len += ssh1_bignum_length(key->exponent);
453 len += ssh1_bignum_length(key->modulus);
454 len += 4 + strlen(key->comment);
455 }
456
457 /*
458 * Packet header is the obvious five bytes, plus four
459 * bytes for the key count.
460 */
461 len += 5 + 4;
462 if (len > AGENT_MAX_MSGLEN)
463 goto failure; /* aaargh! too much stuff! */
464 PUT_32BIT(ret, len-4);
465 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
466 PUT_32BIT(ret+5, nkeys);
467 p = ret + 5 + 4;
468 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
469 PUT_32BIT(p, bignum_bitcount(key->modulus));
470 p += 4;
471 p += ssh1_write_bignum(p, key->exponent);
472 p += ssh1_write_bignum(p, key->modulus);
473 PUT_32BIT(p, strlen(key->comment));
474 memcpy(p+4, key->comment, strlen(key->comment));
475 p += 4 + strlen(key->comment);
476 }
477 }
478 break;
479 case SSH2_AGENTC_REQUEST_IDENTITIES:
480 /*
481 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
482 */
483 {
484 struct ssh2_userkey *key;
485 int len, nkeys;
486 unsigned char *blob;
487 int bloblen;
488 int i;
489
490 /*
491 * Count up the number and length of keys we hold.
492 */
493 len = nkeys = 0;
494 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
495 nkeys++;
496 len += 4; /* length field */
497 blob = key->alg->public_blob(key->data, &bloblen);
498 len += bloblen;
499 sfree(blob);
500 len += 4 + strlen(key->comment);
501 }
502
503 /*
504 * Packet header is the obvious five bytes, plus four
505 * bytes for the key count.
506 */
507 len += 5 + 4;
508 if (len > AGENT_MAX_MSGLEN)
509 goto failure; /* aaargh! too much stuff! */
510 PUT_32BIT(ret, len-4);
511 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
512 PUT_32BIT(ret+5, nkeys);
513 p = ret + 5 + 4;
514 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
515 blob = key->alg->public_blob(key->data, &bloblen);
516 PUT_32BIT(p, bloblen);
517 p += 4;
518 memcpy(p, blob, bloblen);
519 p += bloblen;
520 sfree(blob);
521 PUT_32BIT(p, strlen(key->comment));
522 memcpy(p+4, key->comment, strlen(key->comment));
523 p += 4 + strlen(key->comment);
524 }
525 }
526 break;
527 case SSH1_AGENTC_RSA_CHALLENGE:
528 /*
529 * Reply with either SSH1_AGENT_RSA_RESPONSE or
530 * SSH_AGENT_FAILURE, depending on whether we have that key
531 * or not.
532 */
533 {
534 struct RSAKey reqkey, *key;
535 Bignum challenge, response;
536 unsigned char response_source[48], response_md5[16];
537 struct MD5Context md5c;
538 int i, len;
539
540 p += 4;
541 p += ssh1_read_bignum(p, &reqkey.exponent);
542 p += ssh1_read_bignum(p, &reqkey.modulus);
543 p += ssh1_read_bignum(p, &challenge);
544 memcpy(response_source+32, p, 16); p += 16;
545 if (GET_32BIT(p) != 1 ||
546 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
547 freebn(reqkey.exponent);
548 freebn(reqkey.modulus);
549 freebn(challenge);
550 goto failure;
551 }
552 response = rsadecrypt(challenge, key);
553 for (i = 0; i < 32; i++)
554 response_source[i] = bignum_byte(response, 31-i);
555
556 MD5Init(&md5c);
557 MD5Update(&md5c, response_source, 48);
558 MD5Final(response_md5, &md5c);
559 memset(response_source, 0, 48); /* burn the evidence */
560 freebn(response); /* and that evidence */
561 freebn(challenge); /* yes, and that evidence */
562 freebn(reqkey.exponent); /* and free some memory ... */
563 freebn(reqkey.modulus); /* ... while we're at it. */
564
565 /*
566 * Packet is the obvious five byte header, plus sixteen
567 * bytes of MD5.
568 */
569 len = 5 + 16;
570 PUT_32BIT(ret, len-4);
571 ret[4] = SSH1_AGENT_RSA_RESPONSE;
572 memcpy(ret+5, response_md5, 16);
573 }
574 break;
575 case SSH2_AGENTC_SIGN_REQUEST:
576 /*
577 * Reply with either SSH2_AGENT_RSA_RESPONSE or
578 * SSH_AGENT_FAILURE, depending on whether we have that key
579 * or not.
580 */
581 {
582 struct ssh2_userkey *key;
583 struct blob b;
584 unsigned char *data, *signature;
585 int datalen, siglen, len;
586
587 b.len = GET_32BIT(p);
588 p += 4;
589 b.blob = p;
590 p += b.len;
591 datalen = GET_32BIT(p);
592 p += 4;
593 data = p;
594 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
595 if (!key)
596 goto failure;
597 signature = key->alg->sign(key->data, data, datalen, &siglen);
598 len = 5+4+siglen;
599 PUT_32BIT(ret, len-4);
600 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
601 PUT_32BIT(ret+5, siglen);
602 memcpy(ret+5+4, signature, siglen);
603 sfree(signature);
604 }
605 break;
606 case SSH1_AGENTC_ADD_RSA_IDENTITY:
607 /*
608 * Add to the list and return SSH_AGENT_SUCCESS, or
609 * SSH_AGENT_FAILURE if the key was malformed.
610 */
611 {
612 struct RSAKey *key;
613 char *comment;
614 key = smalloc(sizeof(struct RSAKey));
615 memset(key, 0, sizeof(key));
616 p += makekey(p, key, NULL, 1);
617 p += makeprivate(p, key);
618 p += ssh1_read_bignum(p, key->iqmp); /* p^-1 mod q */
619 p += ssh1_read_bignum(p, key->p); /* p */
620 p += ssh1_read_bignum(p, key->q); /* q */
621 comment = smalloc(GET_32BIT(p));
622 if (comment) {
623 memcpy(comment, p+4, GET_32BIT(p));
624 key->comment = comment;
625 }
626 PUT_32BIT(ret, 1);
627 ret[4] = SSH_AGENT_FAILURE;
628 if (add234(rsakeys, key) == key) {
629 keylist_update();
630 ret[4] = SSH_AGENT_SUCCESS;
631 } else {
632 freersakey(key);
633 sfree(key);
634 }
635 }
636 break;
637 case SSH2_AGENTC_ADD_IDENTITY:
638 /*
639 * Add to the list and return SSH_AGENT_SUCCESS, or
640 * SSH_AGENT_FAILURE if the key was malformed.
641 */
642 {
643 struct ssh2_userkey *key;
644 char *comment, *alg;
645 int alglen, commlen;
646 int bloblen;
647
648 key = smalloc(sizeof(struct ssh2_userkey));
649
650 alglen = GET_32BIT(p); p += 4;
651 alg = p; p += alglen;
652 /* Add further algorithm names here. */
653 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
654 key->alg = &ssh_rsa;
655 else {
656 sfree(key);
657 goto failure;
658 }
659
660 bloblen = GET_32BIT((unsigned char *)msg) - (p-(unsigned char *)msg-4);
661 key->data = key->alg->openssh_createkey(&p, &bloblen);
662 if (!key->data) {
663 sfree(key);
664 goto failure;
665 }
666 commlen = GET_32BIT(p); p += 4;
667
668 comment = smalloc(commlen+1);
669 if (comment) {
670 memcpy(comment, p, commlen);
671 comment[commlen] = '\0';
672 }
673 key->comment = comment;
674
675 PUT_32BIT(ret, 1);
676 ret[4] = SSH_AGENT_FAILURE;
677 if (add234(ssh2keys, key) == key) {
678 keylist_update();
679 ret[4] = SSH_AGENT_SUCCESS;
680 } else {
681 key->alg->freekey(key->data);
682 sfree(key->comment);
683 sfree(key);
684 }
685 }
686 break;
687 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
688 /*
689 * Remove from the list and return SSH_AGENT_SUCCESS, or
690 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
691 * start with.
692 */
693 {
694 struct RSAKey reqkey, *key;
695
696 p += makekey(p, &reqkey, NULL, 0);
697 key = find234(rsakeys, &reqkey, NULL);
698 freebn(reqkey.exponent);
699 freebn(reqkey.modulus);
700 PUT_32BIT(ret, 1);
701 ret[4] = SSH_AGENT_FAILURE;
702 if (key) {
703 del234(rsakeys, key);
704 keylist_update();
705 freersakey(key);
706 sfree(key);
707 ret[4] = SSH_AGENT_SUCCESS;
708 }
709 }
710 break;
711 case SSH2_AGENTC_REMOVE_IDENTITY:
712 /*
713 * Remove from the list and return SSH_AGENT_SUCCESS, or
714 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
715 * start with.
716 */
717 {
718 struct ssh2_userkey *key;
719 struct blob b;
720
721 b.len = GET_32BIT(p);
722 p += 4;
723 b.blob = p;
724 p += b.len;
725 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
726 if (!key)
727 goto failure;
728
729 PUT_32BIT(ret, 1);
730 ret[4] = SSH_AGENT_FAILURE;
731 if (key) {
732 del234(ssh2keys, key);
733 keylist_update();
734 key->alg->freekey(key->data);
735 sfree(key);
736 ret[4] = SSH_AGENT_SUCCESS;
737 }
738 }
739 break;
740 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
741 /*
742 * Remove all SSH1 keys. Always returns success.
743 */
744 {
745 struct RSAKey *rkey;
746
747 while ( (rkey = index234(rsakeys, 0)) != NULL ) {
748 del234(rsakeys, rkey);
749 freersakey(rkey);
750 sfree(rkey);
751 }
752 keylist_update();
753
754 PUT_32BIT(ret, 1);
755 ret[4] = SSH_AGENT_SUCCESS;
756 }
757 break;
758 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
759 /*
760 * Remove all SSH2 keys. Always returns success.
761 */
762 {
763 struct ssh2_userkey *skey;
764
765 while ( (skey = index234(ssh2keys, 0)) != NULL ) {
766 del234(ssh2keys, skey);
767 skey->alg->freekey(skey->data);
768 sfree(skey);
769 }
770 keylist_update();
771
772 PUT_32BIT(ret, 1);
773 ret[4] = SSH_AGENT_SUCCESS;
774 }
775 break;
776 default:
777 failure:
778 /*
779 * Unrecognised message. Return SSH_AGENT_FAILURE.
780 */
781 PUT_32BIT(ret, 1);
782 ret[4] = SSH_AGENT_FAILURE;
783 break;
784 }
785 }
786
787 /*
788 * Key comparison function for the 2-3-4 tree of RSA keys.
789 */
790 static int cmpkeys_rsa(void *av, void *bv) {
791 struct RSAKey *a = (struct RSAKey *)av;
792 struct RSAKey *b = (struct RSAKey *)bv;
793 Bignum am, bm;
794 int alen, blen;
795
796 am = a->modulus;
797 bm = b->modulus;
798 /*
799 * Compare by length of moduli.
800 */
801 alen = bignum_bitcount(am);
802 blen = bignum_bitcount(bm);
803 if (alen > blen) return +1; else if (alen < blen) return -1;
804 /*
805 * Now compare by moduli themselves.
806 */
807 alen = (alen + 7) / 8; /* byte count */
808 while (alen-- > 0) {
809 int abyte, bbyte;
810 abyte = bignum_byte(am, alen);
811 bbyte = bignum_byte(bm, alen);
812 if (abyte > bbyte) return +1; else if (abyte < bbyte) return -1;
813 }
814 /*
815 * Give up.
816 */
817 return 0;
818 }
819
820 /*
821 * Key comparison function for the 2-3-4 tree of SSH2 keys.
822 */
823 static int cmpkeys_ssh2(void *av, void *bv) {
824 struct ssh2_userkey *a = (struct ssh2_userkey *)av;
825 struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
826 int i;
827 int alen, blen;
828 unsigned char *ablob, *bblob;
829 int c;
830
831 /*
832 * Compare purely by public blob.
833 */
834 ablob = a->alg->public_blob(a->data, &alen);
835 bblob = b->alg->public_blob(b->data, &blen);
836
837 c = 0;
838 for (i = 0; i < alen && i < blen; i++) {
839 if (ablob[i] < bblob[i]) {
840 c = -1; break;
841 } else if (ablob[i] > bblob[i]) {
842 c = +1; break;
843 }
844 }
845 if (c == 0 && i < alen) c = +1; /* a is longer */
846 if (c == 0 && i < blen) c = -1; /* a is longer */
847
848 sfree(ablob);
849 sfree(bblob);
850
851 return c;
852 }
853
854 /*
855 * Key comparison function for looking up a blob in the 2-3-4 tree
856 * of SSH2 keys.
857 */
858 static int cmpkeys_ssh2_asymm(void *av, void *bv) {
859 struct blob *a = (struct blob *)av;
860 struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
861 int i;
862 int alen, blen;
863 unsigned char *ablob, *bblob;
864 int c;
865
866 /*
867 * Compare purely by public blob.
868 */
869 ablob = a->blob;
870 alen = a->len;
871 bblob = b->alg->public_blob(b->data, &blen);
872
873 c = 0;
874 for (i = 0; i < alen && i < blen; i++) {
875 if (ablob[i] < bblob[i]) {
876 c = -1; break;
877 } else if (ablob[i] > bblob[i]) {
878 c = +1; break;
879 }
880 }
881 if (c == 0 && i < alen) c = +1; /* a is longer */
882 if (c == 0 && i < blen) c = -1; /* a is longer */
883
884 sfree(bblob);
885
886 return c;
887 }
888
889 static void error(char *s) {
890 MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR);
891 }
892
893 /*
894 * Prompt for a key file to add, and add it.
895 */
896 static void prompt_add_keyfile(void) {
897 OPENFILENAME of;
898 char filename[FILENAME_MAX];
899 memset(&of, 0, sizeof(of));
900 #ifdef OPENFILENAME_SIZE_VERSION_400
901 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
902 #else
903 of.lStructSize = sizeof(of);
904 #endif
905 of.hwndOwner = hwnd;
906 of.lpstrFilter = "All Files\0*\0\0\0";
907 of.lpstrCustomFilter = NULL;
908 of.nFilterIndex = 1;
909 of.lpstrFile = filename; *filename = '\0';
910 of.nMaxFile = sizeof(filename);
911 of.lpstrFileTitle = NULL;
912 of.lpstrInitialDir = NULL;
913 of.lpstrTitle = "Select Private Key File";
914 of.Flags = 0;
915 if (GetOpenFileName(&of)) {
916 add_keyfile(filename);
917 keylist_update();
918 }
919 }
920
921 /*
922 * Dialog-box function for the key list box.
923 */
924 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
925 WPARAM wParam, LPARAM lParam) {
926 struct RSAKey *rkey;
927 struct ssh2_userkey *skey;
928
929 switch (msg) {
930 case WM_INITDIALOG:
931 /*
932 * Centre the window.
933 */
934 { /* centre the window */
935 RECT rs, rd;
936 HWND hw;
937
938 hw = GetDesktopWindow();
939 if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
940 MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
941 (rs.bottom + rs.top + rd.top - rd.bottom)/2,
942 rd.right-rd.left, rd.bottom-rd.top, TRUE);
943 }
944
945 keylist = hwnd;
946 {
947 static int tabs[] = {35, 60, 210};
948 SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS,
949 sizeof(tabs)/sizeof(*tabs), (LPARAM) tabs);
950 }
951 keylist_update();
952 return 0;
953 case WM_COMMAND:
954 switch (LOWORD(wParam)) {
955 case IDOK:
956 case IDCANCEL:
957 keylist = NULL;
958 DestroyWindow(hwnd);
959 return 0;
960 case 101: /* add key */
961 if (HIWORD(wParam) == BN_CLICKED ||
962 HIWORD(wParam) == BN_DOUBLECLICKED) {
963 prompt_add_keyfile();
964 }
965 return 0;
966 case 102: /* remove key */
967 if (HIWORD(wParam) == BN_CLICKED ||
968 HIWORD(wParam) == BN_DOUBLECLICKED) {
969 int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0);
970 int i;
971 if (n == LB_ERR) {
972 MessageBeep(0);
973 break;
974 }
975 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++)
976 if (n-- == 0)
977 break;
978 if (rkey) {
979 del234(rsakeys, rkey);
980 freersakey(rkey);
981 sfree(rkey);
982 } else {
983 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++)
984 if (n-- == 0)
985 break;
986 if (skey) {
987 del234(ssh2keys, skey);
988 skey->alg->freekey(skey->data);
989 sfree(skey);
990 }
991 }
992 keylist_update();
993 }
994 return 0;
995 }
996 return 0;
997 case WM_CLOSE:
998 keylist = NULL;
999 DestroyWindow(hwnd);
1000 return 0;
1001 }
1002 return 0;
1003 }
1004
1005 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
1006 WPARAM wParam, LPARAM lParam) {
1007 int ret;
1008 static int menuinprogress;
1009
1010 switch (message) {
1011 case WM_SYSTRAY:
1012 if (lParam == WM_RBUTTONUP) {
1013 POINT cursorpos;
1014 GetCursorPos(&cursorpos);
1015 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1016 } else if (lParam == WM_LBUTTONDBLCLK) {
1017 /* Equivalent to IDM_VIEWKEYS. */
1018 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1019 }
1020 break;
1021 case WM_SYSTRAY2:
1022 if (!menuinprogress) {
1023 menuinprogress = 1;
1024 SetForegroundWindow(hwnd);
1025 ret = TrackPopupMenu(systray_menu,
1026 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1027 TPM_RIGHTBUTTON,
1028 wParam, lParam, 0, hwnd, NULL);
1029 menuinprogress = 0;
1030 }
1031 break;
1032 case WM_COMMAND:
1033 case WM_SYSCOMMAND:
1034 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1035 case IDM_CLOSE:
1036 SendMessage(hwnd, WM_CLOSE, 0, 0);
1037 break;
1038 case IDM_VIEWKEYS:
1039 if (!keylist) {
1040 keylist = CreateDialog (instance, MAKEINTRESOURCE(211),
1041 NULL, KeyListProc);
1042 ShowWindow (keylist, SW_SHOWNORMAL);
1043 /*
1044 * Sometimes the window comes up minimised / hidden
1045 * for no obvious reason. Prevent this.
1046 */
1047 SetForegroundWindow(keylist);
1048 SetWindowPos (keylist, HWND_TOP, 0, 0, 0, 0,
1049 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1050 }
1051 break;
1052 case IDM_ADDKEY:
1053 prompt_add_keyfile();
1054 break;
1055 case IDM_ABOUT:
1056 if (!aboutbox) {
1057 aboutbox = CreateDialog (instance, MAKEINTRESOURCE(213),
1058 NULL, AboutProc);
1059 ShowWindow (aboutbox, SW_SHOWNORMAL);
1060 /*
1061 * Sometimes the window comes up minimised / hidden
1062 * for no obvious reason. Prevent this.
1063 */
1064 SetForegroundWindow(aboutbox);
1065 SetWindowPos (aboutbox, HWND_TOP, 0, 0, 0, 0,
1066 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1067 }
1068 break;
1069 }
1070 break;
1071 case WM_DESTROY:
1072 PostQuitMessage (0);
1073 return 0;
1074 case WM_COPYDATA:
1075 {
1076 COPYDATASTRUCT *cds;
1077 char *mapname;
1078 void *p;
1079 HANDLE filemap, proc;
1080 PSID mapowner, procowner;
1081 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1082 int ret = 0;
1083
1084 cds = (COPYDATASTRUCT *)lParam;
1085 if (cds->dwData != AGENT_COPYDATA_ID)
1086 return 0; /* not our message, mate */
1087 mapname = (char *)cds->lpData;
1088 if (mapname[cds->cbData - 1] != '\0')
1089 return 0; /* failure to be ASCIZ! */
1090 #ifdef DEBUG_IPC
1091 debug(("mapname is :%s:\r\n", mapname));
1092 #endif
1093 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1094 #ifdef DEBUG_IPC
1095 debug(("filemap is %p\r\n", filemap));
1096 #endif
1097 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1098 int rc;
1099 #ifndef NO_SECURITY
1100 if (has_security) {
1101 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1102 GetCurrentProcessId())) == NULL) {
1103 #ifdef DEBUG_IPC
1104 debug(("couldn't get handle for process\r\n"));
1105 #endif
1106 return 0;
1107 }
1108 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1109 OWNER_SECURITY_INFORMATION,
1110 &procowner, NULL, NULL, NULL,
1111 &psd2) != ERROR_SUCCESS) {
1112 #ifdef DEBUG_IPC
1113 debug(("couldn't get owner info for process\r\n"));
1114 #endif
1115 CloseHandle(proc);
1116 return 0; /* unable to get security info */
1117 }
1118 CloseHandle(proc);
1119 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1120 OWNER_SECURITY_INFORMATION,
1121 &mapowner, NULL, NULL, NULL,
1122 &psd1) != ERROR_SUCCESS)) {
1123 #ifdef DEBUG_IPC
1124 debug(("couldn't get owner info for filemap: %d\r\n", rc));
1125 #endif
1126 return 0;
1127 }
1128 #ifdef DEBUG_IPC
1129 debug(("got security stuff\r\n"));
1130 #endif
1131 if (!EqualSid(mapowner, procowner))
1132 return 0; /* security ID mismatch! */
1133 #ifdef DEBUG_IPC
1134 debug(("security stuff matched\r\n"));
1135 #endif
1136 LocalFree(psd1);
1137 LocalFree(psd2);
1138 } else {
1139 #ifdef DEBUG_IPC
1140 debug(("security APIs not present\r\n"));
1141 #endif
1142 }
1143 #endif
1144 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1145 #ifdef DEBUG_IPC
1146 debug(("p is %p\r\n", p));
1147 {int i; for(i=0;i<5;i++)debug(("p[%d]=%02x\r\n", i, ((unsigned char *)p)[i]));}
1148 #endif
1149 answer_msg(p);
1150 ret = 1;
1151 UnmapViewOfFile(p);
1152 }
1153 CloseHandle(filemap);
1154 return ret;
1155 }
1156 }
1157
1158 return DefWindowProc (hwnd, message, wParam, lParam);
1159 }
1160
1161 /*
1162 * Fork and Exec the command in cmdline. [DBW]
1163 */
1164 void spawn_cmd(char *cmdline, int show) {
1165 if (ShellExecute(NULL, _T("open"), cmdline,
1166 NULL, NULL, show) <= (HINSTANCE) 32) {
1167 TCHAR sMsg[140];
1168 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
1169 GetLastError());
1170 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1171 }
1172 }
1173
1174 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
1175 WNDCLASS wndclass;
1176 MSG msg;
1177 OSVERSIONINFO osi;
1178 HMODULE advapi;
1179 char *command = NULL;
1180 int added_keys = 0;
1181
1182 /*
1183 * Determine whether we're an NT system (should have security
1184 * APIs) or a non-NT system (don't do security).
1185 */
1186 memset(&osi, 0, sizeof(OSVERSIONINFO));
1187 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1188 if (GetVersionEx(&osi) && osi.dwPlatformId==VER_PLATFORM_WIN32_NT) {
1189 has_security = TRUE;
1190 } else
1191 has_security = FALSE;
1192
1193 if (has_security) {
1194 #ifndef NO_SECURITY
1195 /*
1196 * Attempt to get the security API we need.
1197 */
1198 advapi = LoadLibrary("ADVAPI32.DLL");
1199 getsecurityinfo = (gsi_fn_t)GetProcAddress(advapi, "GetSecurityInfo");
1200 if (!getsecurityinfo) {
1201 MessageBox(NULL,
1202 "Unable to access security APIs. Pageant will\n"
1203 "not run, in case it causes a security breach.",
1204 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1205 return 1;
1206 }
1207 #else
1208 MessageBox(NULL,
1209 "This program has been compiled for Win9X and will\n"
1210 "not run on NT, in case it causes a security breach.",
1211 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1212 return 1;
1213 #endif
1214 } else
1215 advapi = NULL;
1216
1217 instance = inst;
1218
1219 /*
1220 * Find out if Pageant is already running.
1221 */
1222 already_running = FALSE;
1223 if (FindWindow("Pageant", "Pageant"))
1224 already_running = TRUE;
1225 else {
1226
1227 if (!prev) {
1228 wndclass.style = 0;
1229 wndclass.lpfnWndProc = WndProc;
1230 wndclass.cbClsExtra = 0;
1231 wndclass.cbWndExtra = 0;
1232 wndclass.hInstance = inst;
1233 wndclass.hIcon = LoadIcon (inst,
1234 MAKEINTRESOURCE(IDI_MAINICON));
1235 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
1236 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
1237 wndclass.lpszMenuName = NULL;
1238 wndclass.lpszClassName = APPNAME;
1239
1240 RegisterClass (&wndclass);
1241 }
1242
1243 hwnd = keylist = NULL;
1244
1245 hwnd = CreateWindow (APPNAME, APPNAME,
1246 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1247 CW_USEDEFAULT, CW_USEDEFAULT,
1248 100, 100, NULL, NULL, inst, NULL);
1249
1250 /* Set up a system tray icon */
1251 {
1252 BOOL res;
1253 NOTIFYICONDATA tnid;
1254 HICON hicon;
1255
1256 #ifdef NIM_SETVERSION
1257 tnid.uVersion = 0;
1258 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1259 #endif
1260
1261 tnid.cbSize = sizeof(NOTIFYICONDATA);
1262 tnid.hWnd = hwnd;
1263 tnid.uID = 1; /* unique within this systray use */
1264 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1265 tnid.uCallbackMessage = WM_SYSTRAY;
1266 tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
1267 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1268
1269 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1270
1271 if (hicon)
1272 DestroyIcon(hicon);
1273
1274 systray_menu = CreatePopupMenu();
1275 /* accelerators used: vkxa */
1276 AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
1277 AppendMenu (systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1278 AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1279 AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1280 }
1281
1282 ShowWindow (hwnd, SW_HIDE);
1283
1284 /*
1285 * Initialise storage for RSA keys.
1286 */
1287 rsakeys = newtree234(cmpkeys_rsa);
1288 ssh2keys = newtree234(cmpkeys_ssh2);
1289
1290 }
1291
1292 /*
1293 * Process the command line and add keys as listed on it.
1294 * If we already determined that we need to spawn a program from above we
1295 * need to ignore the first two arguments. [DBW]
1296 */
1297 {
1298 char *p;
1299 int inquotes = 0;
1300 int ignorearg = 0;
1301 p = cmdline;
1302 while (*p) {
1303 while (*p && isspace(*p)) p++;
1304 if (*p && !isspace(*p)) {
1305 char *q = p, *pp = p;
1306 while (*p && (inquotes || !isspace(*p)))
1307 {
1308 if (*p == '"') {
1309 inquotes = !inquotes;
1310 p++;
1311 continue;
1312 }
1313 *pp++ = *p++;
1314 }
1315 if (*pp) {
1316 if (*p) p++;
1317 *pp++ = '\0';
1318 }
1319 if (!strcmp(q, "-c")) {
1320 /*
1321 * If we see `-c', then the rest of the
1322 * command line should be treated as a
1323 * command to be spawned.
1324 */
1325 while (*p && isspace(*p)) p++;
1326 command = p;
1327 break;
1328 } else {
1329 add_keyfile(q);
1330 added_keys = TRUE;
1331 }
1332 }
1333 }
1334 }
1335
1336 if (command) spawn_cmd (command, show);
1337
1338 /*
1339 * If Pageant was already running, we leave now. If we haven't
1340 * even taken any auxiliary action (spawned a command or added
1341 * keys), complain.
1342 */
1343 if (already_running) {
1344 if (!command && !added_keys) {
1345 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1346 MB_ICONERROR | MB_OK);
1347 }
1348 if (advapi) FreeLibrary(advapi);
1349 return 0;
1350 }
1351
1352 /*
1353 * Main message loop.
1354 */
1355 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1356 TranslateMessage(&msg);
1357 DispatchMessage(&msg);
1358 }
1359
1360 /* Clean up the system tray icon */
1361 {
1362 NOTIFYICONDATA tnid;
1363
1364 tnid.cbSize = sizeof(NOTIFYICONDATA);
1365 tnid.hWnd = hwnd;
1366 tnid.uID = 1;
1367
1368 Shell_NotifyIcon(NIM_DELETE, &tnid);
1369
1370 DestroyMenu(systray_menu);
1371 }
1372
1373 if (advapi) FreeLibrary(advapi);
1374 exit(msg.wParam);
1375 }