2 * Pageant: the PuTTY Authentication Agent.
15 #define IDI_MAINICON 200
16 #define IDI_TRAYICON 201
18 #define WM_XUSER (WM_USER + 0x2000)
19 #define WM_SYSTRAY (WM_XUSER + 6)
20 #define WM_SYSTRAY2 (WM_XUSER + 7)
22 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
25 * FIXME: maybe some day we can sort this out ...
27 #define AGENT_MAX_MSGLEN 8192
29 #define IDM_CLOSE 0x0010
30 #define IDM_VIEWKEYS 0x0020
31 #define IDM_ADDKEY 0x0030
32 #define IDM_ABOUT 0x0040
34 #define APPNAME "Pageant"
36 #define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1
37 #define SSH_AGENT_RSA_IDENTITIES_ANSWER 2
38 #define SSH_AGENTC_RSA_CHALLENGE 3
39 #define SSH_AGENT_RSA_RESPONSE 4
40 #define SSH_AGENT_FAILURE 5
41 #define SSH_AGENT_SUCCESS 6
42 #define SSH_AGENTC_ADD_RSA_IDENTITY 7
43 #define SSH_AGENTC_REMOVE_RSA_IDENTITY 8
47 static HINSTANCE instance;
51 static HMENU systray_menu;
53 static tree234 *rsakeys;
55 static int has_security;
57 typedef DWORD (WINAPI *gsi_fn_t)
58 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
59 PSID *, PSID *, PACL *, PACL *,
60 PSECURITY_DESCRIPTOR *);
61 static gsi_fn_t getsecurityinfo;
65 * We need this to link with the RSA code, because rsaencrypt()
66 * pads its data with random bytes. Since we only use rsadecrypt(),
67 * which is deterministic, this should never be called.
69 * If it _is_ called, there is a _serious_ problem, because it
70 * won't generate true random numbers. So we must scream, panic,
71 * and exit immediately if that should happen.
73 int random_byte(void) {
74 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
79 * This function is needed to link with the DES code. We need not
80 * have it do anything at all.
82 void logevent(char *msg) {
85 #define GET_32BIT(cp) \
86 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
87 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
88 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
89 ((unsigned long)(unsigned char)(cp)[3]))
91 #define PUT_32BIT(cp, value) { \
92 (cp)[0] = (unsigned char)((value) >> 24); \
93 (cp)[1] = (unsigned char)((value) >> 16); \
94 (cp)[2] = (unsigned char)((value) >> 8); \
95 (cp)[3] = (unsigned char)(value); }
97 #define PASSPHRASE_MAXLEN 512
99 struct PassphraseProcStruct {
105 * Dialog-box function for the Licence box.
107 static int CALLBACK LicenceProc (HWND hwnd, UINT msg,
108 WPARAM wParam, LPARAM lParam) {
113 switch (LOWORD(wParam)) {
127 * Dialog-box function for the About box.
129 static int CALLBACK AboutProc (HWND hwnd, UINT msg,
130 WPARAM wParam, LPARAM lParam) {
133 SetDlgItemText (hwnd, 100, ver);
136 switch (LOWORD(wParam)) {
139 DestroyWindow (hwnd);
142 EnableWindow(hwnd, 0);
143 DialogBox (instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
144 EnableWindow(hwnd, 1);
145 SetActiveWindow(hwnd);
151 DestroyWindow (hwnd);
158 * Dialog-box function for the passphrase box.
160 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
161 WPARAM wParam, LPARAM lParam) {
162 static char *passphrase;
163 struct PassphraseProcStruct *p;
167 SetForegroundWindow(hwnd);
168 SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
169 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
170 p = (struct PassphraseProcStruct *)lParam;
171 passphrase = p->passphrase;
173 SetDlgItemText(hwnd, 101, p->comment);
177 switch (LOWORD(wParam)) {
187 case 102: /* edit box */
188 if (HIWORD(wParam) == EN_CHANGE) {
189 GetDlgItemText (hwnd, 102, passphrase, PASSPHRASE_MAXLEN-1);
190 passphrase[PASSPHRASE_MAXLEN-1] = '\0';
203 * Update the visible key list.
205 static void keylist_update(void) {
210 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
211 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
212 char listentry[512], *p;
214 * Replace two spaces in the fingerprint with tabs, for
215 * nice alignment in the box.
217 rsa_fingerprint(listentry, sizeof(listentry), key);
218 p = strchr(listentry, ' '); if (p) *p = '\t';
219 p = strchr(listentry, ' '); if (p) *p = '\t';
220 SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
221 0, (LPARAM)listentry);
223 SendDlgItemMessage (keylist, 100, LB_SETCURSEL, (WPARAM) -1, 0);
228 * This function loads a key from a file and adds it.
230 static void add_keyfile(char *filename) {
231 char passphrase[PASSPHRASE_MAXLEN];
237 struct PassphraseProcStruct pps;
239 needs_pass = rsakey_encrypted(filename, &comment);
241 key = malloc(sizeof(*key));
242 pps.passphrase = passphrase;
243 pps.comment = comment;
247 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
248 NULL, PassphraseProc,
251 if (comment) free(comment);
253 return; /* operation cancelled */
257 ret = loadrsakey(filename, key, NULL, passphrase);
260 if (comment) free(comment);
262 MessageBox(NULL, "Couldn't load private key.", APPNAME,
263 MB_OK | MB_ICONERROR);
267 if (add234(rsakeys, key) != key)
268 free(key); /* already present, don't waste RAM */
272 * This is the main agent function that answers messages.
274 static void answer_msg(void *msg) {
275 unsigned char *p = msg;
276 unsigned char *ret = msg;
280 * Get the message type.
286 case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
288 * Reply with SSH_AGENT_RSA_IDENTITIES_ANSWER.
296 * Count up the number and length of keys we hold.
299 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
301 len += 4; /* length field */
302 len += ssh1_bignum_length(key->exponent);
303 len += ssh1_bignum_length(key->modulus);
304 len += 4 + strlen(key->comment);
308 * Packet header is the obvious five bytes, plus four
309 * bytes for the key count.
312 if (len > AGENT_MAX_MSGLEN)
313 goto failure; /* aaargh! too much stuff! */
314 PUT_32BIT(ret, len-4);
315 ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER;
316 PUT_32BIT(ret+5, nkeys);
318 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
319 PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
321 p += ssh1_write_bignum(p, key->exponent);
322 p += ssh1_write_bignum(p, key->modulus);
323 PUT_32BIT(p, strlen(key->comment));
324 memcpy(p+4, key->comment, strlen(key->comment));
325 p += 4 + strlen(key->comment);
329 case SSH_AGENTC_RSA_CHALLENGE:
331 * Reply with either SSH_AGENT_RSA_RESPONSE or
332 * SSH_AGENT_FAILURE, depending on whether we have that key
336 struct RSAKey reqkey, *key;
337 Bignum challenge, response;
338 unsigned char response_source[48], response_md5[16];
339 struct MD5Context md5c;
343 p += ssh1_read_bignum(p, &reqkey.exponent);
344 p += ssh1_read_bignum(p, &reqkey.modulus);
345 p += ssh1_read_bignum(p, &challenge);
346 memcpy(response_source+32, p, 16); p += 16;
347 if (GET_32BIT(p) != 1 ||
348 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
349 freebn(reqkey.exponent);
350 freebn(reqkey.modulus);
354 response = rsadecrypt(challenge, key);
355 for (i = 0; i < 32; i++)
356 response_source[i] = bignum_byte(response, 31-i);
359 MD5Update(&md5c, response_source, 48);
360 MD5Final(response_md5, &md5c);
361 memset(response_source, 0, 48); /* burn the evidence */
362 freebn(response); /* and that evidence */
363 freebn(challenge); /* yes, and that evidence */
364 freebn(reqkey.exponent); /* and free some memory ... */
365 freebn(reqkey.modulus); /* ... while we're at it. */
368 * Packet is the obvious five byte header, plus sixteen
372 PUT_32BIT(ret, len-4);
373 ret[4] = SSH_AGENT_RSA_RESPONSE;
374 memcpy(ret+5, response_md5, 16);
377 case SSH_AGENTC_ADD_RSA_IDENTITY:
379 * Add to the list and return SSH_AGENT_SUCCESS, or
380 * SSH_AGENT_FAILURE if the key was malformed.
385 key = malloc(sizeof(struct RSAKey));
386 memset(key, 0, sizeof(key));
387 p += makekey(p, key, NULL, 1);
388 p += makeprivate(p, key);
389 p += ssh1_read_bignum(p, NULL); /* p^-1 mod q */
390 p += ssh1_read_bignum(p, NULL); /* p */
391 p += ssh1_read_bignum(p, NULL); /* q */
392 comment = malloc(GET_32BIT(p));
394 memcpy(comment, p+4, GET_32BIT(p));
395 key->comment = comment;
398 ret[4] = SSH_AGENT_FAILURE;
399 if (add234(rsakeys, key) == key) {
401 ret[4] = SSH_AGENT_SUCCESS;
408 case SSH_AGENTC_REMOVE_RSA_IDENTITY:
410 * Remove from the list and return SSH_AGENT_SUCCESS, or
411 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
415 struct RSAKey reqkey, *key;
417 p += makekey(p, &reqkey, NULL, 0);
418 key = find234(rsakeys, &reqkey, NULL);
419 freebn(reqkey.exponent);
420 freebn(reqkey.modulus);
422 ret[4] = SSH_AGENT_FAILURE;
424 del234(rsakeys, key);
427 ret[4] = SSH_AGENT_SUCCESS;
434 * Unrecognised message. Return SSH_AGENT_FAILURE.
437 ret[4] = SSH_AGENT_FAILURE;
443 * Key comparison function for the 2-3-4 tree of RSA keys.
445 static int cmpkeys(void *av, void *bv) {
446 struct RSAKey *a = (struct RSAKey *)av;
447 struct RSAKey *b = (struct RSAKey *)bv;
454 * Compare by length of moduli.
456 alen = ssh1_bignum_bitcount(am);
457 blen = ssh1_bignum_bitcount(bm);
458 if (alen > blen) return +1; else if (alen < blen) return -1;
460 * Now compare by moduli themselves.
462 alen = (alen + 7) / 8; /* byte count */
465 abyte = bignum_byte(am, alen);
466 bbyte = bignum_byte(bm, alen);
467 if (abyte > bbyte) return +1; else if (abyte < bbyte) return -1;
475 static void error(char *s) {
476 MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR);
480 * Prompt for a key file to add, and add it.
482 static void prompt_add_keyfile(void) {
484 char filename[FILENAME_MAX];
485 memset(&of, 0, sizeof(of));
486 #ifdef OPENFILENAME_SIZE_VERSION_400
487 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
489 of.lStructSize = sizeof(of);
492 of.lpstrFilter = "All Files\0*\0\0\0";
493 of.lpstrCustomFilter = NULL;
495 of.lpstrFile = filename; *filename = '\0';
496 of.nMaxFile = sizeof(filename);
497 of.lpstrFileTitle = NULL;
498 of.lpstrInitialDir = NULL;
499 of.lpstrTitle = "Select Private Key File";
501 if (GetOpenFileName(&of)) {
502 add_keyfile(filename);
508 * Dialog-box function for the key list box.
510 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
511 WPARAM wParam, LPARAM lParam) {
519 static int tabs[2] = {25, 175};
520 SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS, 2,
526 switch (LOWORD(wParam)) {
532 case 101: /* add key */
533 if (HIWORD(wParam) == BN_CLICKED ||
534 HIWORD(wParam) == BN_DOUBLECLICKED) {
535 prompt_add_keyfile();
538 case 102: /* remove key */
539 if (HIWORD(wParam) == BN_CLICKED ||
540 HIWORD(wParam) == BN_DOUBLECLICKED) {
541 int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0);
546 for (key = first234(rsakeys, &e); key; key = next234(&e))
549 del234(rsakeys, key);
550 freersakey(key); free(key);
564 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
565 WPARAM wParam, LPARAM lParam) {
567 static int menuinprogress;
571 if (lParam == WM_RBUTTONUP) {
573 GetCursorPos(&cursorpos);
574 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
575 } else if (lParam == WM_LBUTTONDBLCLK) {
576 /* Equivalent to IDM_VIEWKEYS. */
577 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
581 if (!menuinprogress) {
583 SetForegroundWindow(hwnd);
584 ret = TrackPopupMenu(systray_menu,
585 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
587 wParam, lParam, 0, hwnd, NULL);
593 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
595 SendMessage(hwnd, WM_CLOSE, 0, 0);
599 keylist = CreateDialog (instance, MAKEINTRESOURCE(211),
601 ShowWindow (keylist, SW_SHOWNORMAL);
603 * Sometimes the window comes up minimised / hidden
604 * for no obvious reason. Prevent this.
606 SetForegroundWindow(keylist);
607 SetWindowPos (keylist, HWND_TOP, 0, 0, 0, 0,
608 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
612 prompt_add_keyfile();
616 aboutbox = CreateDialog (instance, MAKEINTRESOURCE(213),
618 ShowWindow (aboutbox, SW_SHOWNORMAL);
620 * Sometimes the window comes up minimised / hidden
621 * for no obvious reason. Prevent this.
623 SetForegroundWindow(aboutbox);
624 SetWindowPos (aboutbox, HWND_TOP, 0, 0, 0, 0,
625 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
638 HANDLE filemap, proc;
639 PSID mapowner, procowner;
640 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
643 cds = (COPYDATASTRUCT *)lParam;
644 if (cds->dwData != AGENT_COPYDATA_ID)
645 return 0; /* not our message, mate */
646 mapname = (char *)cds->lpData;
647 if (mapname[cds->cbData - 1] != '\0')
648 return 0; /* failure to be ASCIZ! */
650 debug(("mapname is :%s:\r\n", mapname));
652 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
654 debug(("filemap is %p\r\n", filemap));
656 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
660 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
661 GetCurrentProcessId())) == NULL) {
663 debug(("couldn't get handle for process\r\n"));
667 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
668 OWNER_SECURITY_INFORMATION,
669 &procowner, NULL, NULL, NULL,
670 &psd2) != ERROR_SUCCESS) {
672 debug(("couldn't get owner info for process\r\n"));
675 return 0; /* unable to get security info */
678 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
679 OWNER_SECURITY_INFORMATION,
680 &mapowner, NULL, NULL, NULL,
681 &psd1) != ERROR_SUCCESS)) {
683 debug(("couldn't get owner info for filemap: %d\r\n", rc));
688 debug(("got security stuff\r\n"));
690 if (!EqualSid(mapowner, procowner))
691 return 0; /* security ID mismatch! */
693 debug(("security stuff matched\r\n"));
699 debug(("security APIs not present\r\n"));
703 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
705 debug(("p is %p\r\n", p));
706 {int i; for(i=0;i<5;i++)debug(("p[%d]=%02x\r\n", i, ((unsigned char *)p)[i]));}
712 CloseHandle(filemap);
717 return DefWindowProc (hwnd, message, wParam, lParam);
720 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
727 * Determine whether we're an NT system (should have security
728 * APIs) or a non-NT system (don't do security).
730 memset(&osi, 0, sizeof(OSVERSIONINFO));
731 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
732 if (GetVersionEx(&osi) && osi.dwPlatformId==VER_PLATFORM_WIN32_NT) {
735 has_security = FALSE;
740 * Attempt to ge the security API we need.
742 advapi = LoadLibrary("ADVAPI32.DLL");
743 getsecurityinfo = (gsi_fn_t)GetProcAddress(advapi, "GetSecurityInfo");
744 if (!getsecurityinfo) {
746 "Unable to access security APIs. Pageant will\n"
747 "not run, in case it causes a security breach.",
748 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
753 "This program has been compiled for Win9X and will\n"
754 "not run on NT, in case it causes a security breach.",
755 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
762 * First bomb out totally if we are already running.
764 if (FindWindow("Pageant", "Pageant")) {
765 MessageBox(NULL, "Pageant is already running", "Pageant Error",
766 MB_ICONERROR | MB_OK);
767 if (advapi) FreeLibrary(advapi);
775 wndclass.lpfnWndProc = WndProc;
776 wndclass.cbClsExtra = 0;
777 wndclass.cbWndExtra = 0;
778 wndclass.hInstance = inst;
779 wndclass.hIcon = LoadIcon (inst,
780 MAKEINTRESOURCE(IDI_MAINICON));
781 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
782 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
783 wndclass.lpszMenuName = NULL;
784 wndclass.lpszClassName = APPNAME;
786 RegisterClass (&wndclass);
789 hwnd = keylist = NULL;
791 hwnd = CreateWindow (APPNAME, APPNAME,
792 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
793 CW_USEDEFAULT, CW_USEDEFAULT,
794 100, 100, NULL, NULL, inst, NULL);
796 /* Set up a system tray icon */
802 #ifdef NIM_SETVERSION
804 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
807 tnid.cbSize = sizeof(NOTIFYICONDATA);
809 tnid.uID = 1; /* unique within this systray use */
810 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
811 tnid.uCallbackMessage = WM_SYSTRAY;
812 tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
813 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
815 res = Shell_NotifyIcon(NIM_ADD, &tnid);
820 systray_menu = CreatePopupMenu();
821 /* accelerators used: vkxa */
822 AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
823 AppendMenu (systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
824 AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
825 AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
828 ShowWindow (hwnd, SW_HIDE);
831 * Initialise storage for RSA keys.
833 rsakeys = newtree234(cmpkeys);
836 * Process the command line and add RSA keys as listed on it.
843 while (*p && isspace(*p)) p++;
844 if (*p && !isspace(*p)) {
845 char *q = p, *pp = p;
846 while (*p && (inquotes || !isspace(*p)))
849 inquotes = !inquotes;
867 while (GetMessage(&msg, NULL, 0, 0) == 1) {
868 TranslateMessage(&msg);
869 DispatchMessage(&msg);
872 /* Clean up the system tray icon */
876 tnid.cbSize = sizeof(NOTIFYICONDATA);
880 Shell_NotifyIcon(NIM_DELETE, &tnid);
882 DestroyMenu(systray_menu);
885 if (advapi) FreeLibrary(advapi);