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 | |
35 | HINSTANCE instance; |
36 | HWND hwnd; |
37 | HWND keylist; |
38 | HMENU systray_menu; |
39 | |
40 | tree234 *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 | */ |
51 | int 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 | */ |
60 | void 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 | */ |
80 | static 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 | */ |
118 | void 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 | */ |
156 | void 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 | */ |
306 | int 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 | |
336 | static 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 | */ |
343 | static 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 | |
425 | static 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 | |
514 | int 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 | } |