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