Source form of win_res.rc
[u/mdw/putty] / pageant.c
CommitLineData
5c58ad2d 1/*
2 * Pageant: the PuTTY Authentication Agent.
3 */
4
5#include <windows.h>
6#include <stdio.h> /* FIXME */
7#include "putty.h" /* FIXME */
8#include "ssh.h"
9#include "tree234.h"
10
11#define IDI_MAINICON 200
12#define IDI_TRAYICON 201
13
14#define WM_XUSER (WM_USER + 0x2000)
15#define WM_SYSTRAY (WM_XUSER + 6)
16#define WM_SYSTRAY2 (WM_XUSER + 7)
17#define WM_CLOSEMEM (WM_XUSER + 10)
18
19#define IDM_CLOSE 0x0010
20#define IDM_VIEWKEYS 0x0020
21
22#define APPNAME "Pageant"
23
24#define MAILSLOTNAME "\\\\.\\mailslot\\pageant_listener"
25
26#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1
27#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2
28#define SSH_AGENTC_RSA_CHALLENGE 3
29#define SSH_AGENT_RSA_RESPONSE 4
30#define SSH_AGENT_FAILURE 5
31#define SSH_AGENT_SUCCESS 6
32#define SSH_AGENTC_ADD_RSA_IDENTITY 7
33#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8
34
35HINSTANCE instance;
36HWND hwnd;
37HWND keylist;
38HMENU systray_menu;
39
40tree234 *rsakeys;
41
42/*
43 * We need this to link with the RSA code, because rsaencrypt()
44 * pads its data with random bytes. Since we only use rsadecrypt(),
45 * which is deterministic, this should never be called.
46 *
47 * If it _is_ called, there is a _serious_ problem, because it
48 * won't generate true random numbers. So we must scream, panic,
49 * and exit immediately if that should happen.
50 */
51int random_byte(void) {
52 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
53 exit(0);
54}
55
56/*
57 * This function is needed to link with the DES code. We need not
58 * have it do anything at all.
59 */
60void logevent(char *msg) {
61}
62
63#define GET_32BIT(cp) \
64 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
65 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
66 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
67 ((unsigned long)(unsigned char)(cp)[3]))
68
69#define PUT_32BIT(cp, value) { \
70 (cp)[0] = (unsigned char)((value) >> 24); \
71 (cp)[1] = (unsigned char)((value) >> 16); \
72 (cp)[2] = (unsigned char)((value) >> 8); \
73 (cp)[3] = (unsigned char)(value); }
74
75#define PASSPHRASE_MAXLEN 512
76
77/*
78 * Dialog-box function for the passphrase box.
79 */
80static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
81 WPARAM wParam, LPARAM lParam) {
82 static char *passphrase;
83
84 switch (msg) {
85 case WM_INITDIALOG:
86 passphrase = (char *)lParam;
87 *passphrase = 0;
88 return 0;
89 case WM_COMMAND:
90 switch (LOWORD(wParam)) {
91 case IDOK:
92 if (*passphrase)
93 EndDialog (hwnd, 1);
94 else
95 MessageBeep (0);
96 return 0;
97 case IDCANCEL:
98 EndDialog (hwnd, 0);
99 return 0;
100 case 102: /* edit box */
101 if (HIWORD(wParam) == EN_CHANGE) {
102 GetDlgItemText (hwnd, 102, passphrase, PASSPHRASE_MAXLEN-1);
103 passphrase[PASSPHRASE_MAXLEN-1] = '\0';
104 }
105 return 0;
106 }
107 return 0;
108 case WM_CLOSE:
109 EndDialog (hwnd, 0);
110 return 0;
111 }
112 return 0;
113}
114
115/*
116 * This function loads a key from a file and adds it.
117 */
118void add_keyfile(char *filename) {
119 char passphrase[PASSPHRASE_MAXLEN];
120 struct RSAKey *key;
121 int needs_pass;
122 int ret;
123 int attempts;
124
125 needs_pass = rsakey_encrypted(filename);
126 attempts = 0;
127 key = malloc(sizeof(*key));
128 do {
129 if (needs_pass) {
130 int dlgret;
131 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
132 NULL, PassphraseProc,
133 (LPARAM)passphrase);
134 if (!dlgret) {
135 free(key);
136 return; /* operation cancelled */
137 }
138 } else
139 *passphrase = '\0';
140 ret = loadrsakey(filename, key, passphrase);
141 attempts++;
142 } while (ret == -1);
143 if (ret == 0) {
144 MessageBox(NULL, "Couldn't load public key.", APPNAME,
145 MB_OK | MB_ICONERROR);
146 free(key);
147 return;
148 }
149 if (add234(rsakeys, key) != key)
150 free(key); /* already present, don't waste RAM */
151}
152
153/*
154 * This is the main agent function that answers messages.
155 */
156void answer_msg(void *in, int inlen, void **out, int *outlen) {
157 unsigned char *ret;
158 unsigned char *p = in;
159 int type;
160
161 *out = NULL; /* default `no go' response */
162
163 /*
164 * Basic sanity checks. len >= 5, and len[0:4] holds len-4.
165 */
166 if (inlen < 5 || GET_32BIT(p) != (unsigned long)(inlen-4))
167 return;
168
169 /*
170 * Get the message type.
171 */
172 type = p[4];
173
174 p += 5;
175
176 switch (type) {
177 case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
178 /*
179 * Reply with SSH_AGENT_RSA_IDENTITIES_ANSWER.
180 */
181 {
182 enum234 e;
183 struct RSAKey *key;
184 int len, nkeys;
185
186 /*
187 * Count up the number and length of keys we hold.
188 */
189 len = nkeys = 0;
190 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
191 nkeys++;
192 len += 4; /* length field */
193 len += ssh1_bignum_length(key->exponent);
194 len += ssh1_bignum_length(key->modulus);
195 len += 4 + strlen(key->comment);
196 }
197
198 /*
199 * Packet header is the obvious five bytes, plus four
200 * bytes for the key count.
201 */
202 len += 5 + 4;
203 if ((ret = malloc(len)) != NULL) {
204 PUT_32BIT(ret, len-4);
205 ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER;
206 PUT_32BIT(ret+5, nkeys);
207 p = ret + 5 + 4;
208 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
209 PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
210 p += 4;
211 p += ssh1_write_bignum(p, key->exponent);
212 p += ssh1_write_bignum(p, key->modulus);
213 PUT_32BIT(p, strlen(key->comment));
214 memcpy(p+4, key->comment, strlen(key->comment));
215 p += 4 + strlen(key->comment);
216 }
217 }
218 }
219 break;
220 case SSH_AGENTC_RSA_CHALLENGE:
221 /*
222 * Reply with either SSH_AGENT_RSA_RESPONSE or
223 * SSH_AGENT_FAILURE, depending on whether we have that key
224 * or not.
225 */
226 {
227 struct RSAKey reqkey, *key;
228 Bignum challenge, response;
229 unsigned char response_source[48], response_md5[16];
230 struct MD5Context md5c;
231 int i, len;
232
233 p += 4;
234 p += ssh1_read_bignum(p, &reqkey.exponent);
235 p += ssh1_read_bignum(p, &reqkey.modulus);
236 p += ssh1_read_bignum(p, &challenge);
237 memcpy(response_source+32, p, 16); p += 16;
238 if (GET_32BIT(p) != 1 ||
239 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
240 freebn(reqkey.exponent);
241 freebn(reqkey.modulus);
242 freebn(challenge);
243 goto failure;
244 }
245 response = rsadecrypt(challenge, key);
246 for (i = 0; i < 32; i++)
247 response_source[i] = bignum_byte(response, 31-i);
248
249 MD5Init(&md5c);
250 MD5Update(&md5c, response_source, 48);
251 MD5Final(response_md5, &md5c);
252 memset(response_source, 0, 48); /* burn the evidence */
253 freebn(response); /* and that evidence */
254 freebn(challenge); /* yes, and that evidence */
255 freebn(reqkey.exponent); /* and free some memory ... */
256 freebn(reqkey.modulus); /* ... while we're at it. */
257
258 /*
259 * Packet is the obvious five byte header, plus sixteen
260 * bytes of MD5.
261 */
262 len = 5 + 16;
263 if ((ret = malloc(len)) != NULL) {
264 PUT_32BIT(ret, len-4);
265 ret[4] = SSH_AGENT_RSA_RESPONSE;
266 memcpy(ret+5, response_md5, 16);
267 }
268 }
269 break;
270#if 0 /* FIXME: implement these */
271 case SSH_AGENTC_ADD_RSA_IDENTITY:
272 /*
273 * Add to the list and return SSH_AGENT_SUCCESS, or
274 * SSH_AGENT_FAILURE if the key was malformed.
275 */
276 break;
277 case SSH_AGENTC_REMOVE_RSA_IDENTITY:
278 /*
279 * Remove from the list and return SSH_AGENT_SUCCESS, or
280 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
281 * start with.
282 */
283 break;
284#endif
285 default:
286 failure:
287 /*
288 * Unrecognised message. Return SSH_AGENT_FAILURE.
289 */
290 if ((ret = malloc(5)) != NULL) {
291 PUT_32BIT(ret, 1);
292 ret[4] = SSH_AGENT_FAILURE;
293 }
294 break;
295 }
296
297 if (ret) {
298 *out = ret;
299 *outlen = 4 + GET_32BIT(ret);
300 }
301}
302
303/*
304 * Key comparison function for the 2-3-4 tree of RSA keys.
305 */
306int cmpkeys(void *av, void *bv) {
307 struct RSAKey *a = (struct RSAKey *)av;
308 struct RSAKey *b = (struct RSAKey *)bv;
309 Bignum am, bm;
310 int alen, blen;
311
312 am = a->modulus;
313 bm = b->modulus;
314 /*
315 * Compare by length of moduli.
316 */
317 alen = ssh1_bignum_bitcount(am);
318 blen = ssh1_bignum_bitcount(bm);
319 if (alen > blen) return +1; else if (alen < blen) return -1;
320 /*
321 * Now compare by moduli themselves.
322 */
323 alen = (alen + 7) / 8; /* byte count */
324 while (alen-- > 0) {
325 int abyte, bbyte;
326 abyte = bignum_byte(am, alen);
327 bbyte = bignum_byte(bm, alen);
328 if (abyte > bbyte) return +1; else if (abyte < bbyte) return -1;
329 }
330 /*
331 * Give up.
332 */
333 return 0;
334}
335
336static void error(char *s) {
337 MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR);
338}
339
340/*
341 * Dialog-box function for the key list box.
342 */
343static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
344 WPARAM wParam, LPARAM lParam) {
345 enum234 e;
346 struct RSAKey *key;
347 OPENFILENAME of;
348 char filename[FILENAME_MAX];
349
350 switch (msg) {
351 case WM_INITDIALOG:
352 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
353 SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
354 0, (LPARAM) key->comment);
355 }
356 return 0;
357 case WM_COMMAND:
358 switch (LOWORD(wParam)) {
359 case IDOK:
360 case IDCANCEL:
361 keylist = NULL;
362 DestroyWindow(hwnd);
363 return 0;
364 case 101: /* add key */
365 if (HIWORD(wParam) == BN_CLICKED ||
366 HIWORD(wParam) == BN_DOUBLECLICKED) {
367 memset(&of, 0, sizeof(of));
368#ifdef OPENFILENAME_SIZE_VERSION_400
369 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
370#else
371 of.lStructSize = sizeof(of);
372#endif
373 of.hwndOwner = hwnd;
374 of.lpstrFilter = "All Files\0*\0\0\0";
375 of.lpstrCustomFilter = NULL;
376 of.nFilterIndex = 1;
377 of.lpstrFile = filename; *filename = '\0';
378 of.nMaxFile = sizeof(filename);
379 of.lpstrFileTitle = NULL;
380 of.lpstrInitialDir = NULL;
381 of.lpstrTitle = "Select Public Key File";
382 of.Flags = 0;
383 if (GetOpenFileName(&of)) {
384 add_keyfile(filename);
385 }
386 SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0);
387 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
388 SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
389 0, (LPARAM) key->comment);
390 }
391 SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0);
392 }
393 return 0;
394 case 102: /* remove key */
395 if (HIWORD(wParam) == BN_CLICKED ||
396 HIWORD(wParam) == BN_DOUBLECLICKED) {
397 int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0);
398 if (n == LB_ERR || n == 0) {
399 MessageBeep(0);
400 break;
401 }
402 for (key = first234(rsakeys, &e); key; key = next234(&e))
403 if (n-- == 0)
404 break;
405 del234(rsakeys, key);
406 freersakey(key); free(key);
407 SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0);
408 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
409 SendDlgItemMessage (hwnd, 100, LB_ADDSTRING,
410 0, (LPARAM) key->comment);
411 }
412 SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0);
413 }
414 return 0;
415 }
416 return 0;
417 case WM_CLOSE:
418 keylist = NULL;
419 DestroyWindow(hwnd);
420 return 0;
421 }
422 return 0;
423}
424
425static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
426 WPARAM wParam, LPARAM lParam) {
427 int ret;
428 static int menuinprogress;
429
430 switch (message) {
431 case WM_SYSTRAY:
432 if (lParam == WM_RBUTTONUP) {
433 POINT cursorpos;
434 GetCursorPos(&cursorpos);
435 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
436 }
437 break;
438 case WM_SYSTRAY2:
439 if (!menuinprogress) {
440 menuinprogress = 1;
441 SetForegroundWindow(hwnd);
442 ret = TrackPopupMenu(systray_menu,
443 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
444 TPM_RIGHTBUTTON,
445 wParam, lParam, 0, hwnd, NULL);
446 menuinprogress = 0;
447 }
448 break;
449 case WM_COMMAND:
450 case WM_SYSCOMMAND:
451 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
452 case IDM_CLOSE:
453 SendMessage(hwnd, WM_CLOSE, 0, 0);
454 break;
455 case IDM_VIEWKEYS:
456 if (!keylist) {
457 keylist = CreateDialog (instance, MAKEINTRESOURCE(211),
458 NULL, KeyListProc);
459 ShowWindow (keylist, SW_SHOWNORMAL);
460 }
461 break;
462 }
463 break;
464 case WM_DESTROY:
465 PostQuitMessage (0);
466 return 0;
467 case WM_COPYDATA:
468 {
469 COPYDATASTRUCT *cds;
470 void *in, *out, *ret;
471 int inlen, outlen;
472 HANDLE filemap;
473 char mapname[64];
474 int id;
475
476 cds = (COPYDATASTRUCT *)lParam;
477 /*
478 * FIXME: use dwData somehow.
479 */
480 in = cds->lpData;
481 inlen = cds->cbData;
482 answer_msg(in, inlen, &out, &outlen);
483 if (out) {
484 id = 0;
485 do {
486 sprintf(mapname, "PageantReply%08x", ++id);
487 filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
488 NULL, PAGE_READWRITE,
489 0, outlen+sizeof(int),
490 mapname);
491 } while (filemap == INVALID_HANDLE_VALUE);
492 ret = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0,
493 outlen+sizeof(int));
494 if (ret) {
495 *((int *)ret) = outlen;
496 memcpy(((int *)ret)+1, out, outlen);
497 UnmapViewOfFile(ret);
498 return id;
499 }
500 } else
501 return 0; /* invalid request */
502 }
503 break;
504 case WM_CLOSEMEM:
505 /*
506 * FIXME!
507 */
508 break;
509 }
510
511 return DefWindowProc (hwnd, message, wParam, lParam);
512}
513
514int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
515 WNDCLASS wndclass;
516 HANDLE mailslot;
517 MSG msg;
518
519 instance = inst;
520
521 if (!prev) {
522 wndclass.style = 0;
523 wndclass.lpfnWndProc = WndProc;
524 wndclass.cbClsExtra = 0;
525 wndclass.cbWndExtra = 0;
526 wndclass.hInstance = inst;
527 wndclass.hIcon = LoadIcon (inst,
528 MAKEINTRESOURCE(IDI_MAINICON));
529 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
530 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
531 wndclass.lpszMenuName = NULL;
532 wndclass.lpszClassName = APPNAME;
533
534 RegisterClass (&wndclass);
535 }
536
537 hwnd = keylist = NULL;
538
539 hwnd = CreateWindow (APPNAME, APPNAME,
540 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
541 CW_USEDEFAULT, CW_USEDEFAULT,
542 100, 100, NULL, NULL, inst, NULL);
543
544 /* Set up a system tray icon */
545 {
546 BOOL res;
547 NOTIFYICONDATA tnid;
548 HICON hicon;
549
550#ifdef NIM_SETVERSION
551 tnid.uVersion = 0;
552 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
553#endif
554
555 tnid.cbSize = sizeof(NOTIFYICONDATA);
556 tnid.hWnd = hwnd;
557 tnid.uID = 1; /* unique within this systray use */
558 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
559 tnid.uCallbackMessage = WM_SYSTRAY;
560 tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
561 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
562
563 res = Shell_NotifyIcon(NIM_ADD, &tnid);
564
565 if (hicon)
566 DestroyIcon(hicon);
567
568 systray_menu = CreatePopupMenu();
569 AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "View Keys");
570 AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "Terminate");
571 }
572
573 ShowWindow (hwnd, SW_HIDE);
574
575 /*
576 * Create the mailslot.
577 */
578 {
579 SECURITY_ATTRIBUTES sa;
580 sa.nLength = sizeof(sa);
581 sa.lpSecurityDescriptor = NULL;
582 sa.bInheritHandle = TRUE;
583 mailslot = CreateMailslot(MAILSLOTNAME, 0, 0, &sa);
584 }
585
586 /*
587 * Initialise storage for RSA keys.
588 */
589 rsakeys = newtree234(cmpkeys);
590
591 while (GetMessage(&msg, NULL, 0, 0) == 1) {
592 TranslateMessage(&msg);
593 DispatchMessage(&msg);
594 }
595
596 /* Clean up the system tray icon */
597 {
598 NOTIFYICONDATA tnid;
599
600 tnid.cbSize = sizeof(NOTIFYICONDATA);
601 tnid.hWnd = hwnd;
602 tnid.uID = 1;
603
604 Shell_NotifyIcon(NIM_DELETE, &tnid);
605
606 DestroyMenu(systray_menu);
607 }
608
609 exit(msg.wParam);
610}