From 5c58ad2d1527936b4d30891497eef48bde0fe1f3 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 14 Sep 2000 15:02:50 +0000 Subject: [PATCH] Added Pageant, a first-attempt PuTTY authentication agent git-svn-id: svn://svn.tartarus.org/sgt/putty@589 cda61777-01e9-0310-a592-d414129be87e --- Makefile | 36 +++- pageant.c | 610 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pageant.ico | Bin 0 -> 1078 bytes pageant.rc | 31 +++ pageantc.c | 167 ++++++++++++++++ pageants.ico | Bin 0 -> 318 bytes putty.h | 6 + ssh.c | 122 ++++++++++-- ssh.h | 8 +- sshbn.c | 47 +++++ sshpubk.c | 14 +- sshrsa.c | 7 + 12 files changed, 1020 insertions(+), 28 deletions(-) create mode 100644 pageant.c create mode 100644 pageant.ico create mode 100644 pageant.rc create mode 100644 pageantc.c create mode 100644 pageants.ico diff --git a/Makefile b/Makefile index 270cb5cd..38d9ff5e 100644 --- a/Makefile +++ b/Makefile @@ -50,23 +50,28 @@ GOBJS2 = xlat.$(OBJ) sizetip.$(OBJ) ##-- objects putty puttytel plink LOBJS1 = telnet.$(OBJ) raw.$(OBJ) ldisc.$(OBJ) ##-- objects putty plink -POBJS = ssh.$(OBJ) be_all.$(OBJ) +POBJS = be_all.$(OBJ) ##-- objects puttytel TOBJS = be_nossh.$(OBJ) ##-- objects plink PLOBJS = plink.$(OBJ) windlg.$(OBJ) ##-- objects pscp -SOBJS = scp.$(OBJ) windlg.$(OBJ) ssh.$(OBJ) be_none.$(OBJ) +SOBJS = scp.$(OBJ) windlg.$(OBJ) be_none.$(OBJ) ##-- objects putty puttytel pscp plink MOBJS = misc.$(OBJ) version.$(OBJ) ##-- objects putty pscp plink OBJS1 = sshcrc.$(OBJ) sshdes.$(OBJ) sshmd5.$(OBJ) sshrsa.$(OBJ) sshrand.$(OBJ) OBJS2 = sshsha.$(OBJ) sshblowf.$(OBJ) noise.$(OBJ) sshdh.$(OBJ) sshdss.$(OBJ) -OBJS3 = sshbn.$(OBJ) sshpubk.$(OBJ) +OBJS3 = sshbn.$(OBJ) sshpubk.$(OBJ) ssh.$(OBJ) pageantc.$(OBJ) +##-- objects pageant +PAGE1 = pageant.$(OBJ) sshrsa.$(OBJ) sshpubk.$(OBJ) sshdes.$(OBJ) sshbn.$(OBJ) +PAGE2 = sshmd5.$(OBJ) version.$(OBJ) tree234.$(OBJ) ##-- resources putty PRESRC = win_res.$(RES) ##-- resources puttytel TRESRC = nosshres.$(RES) +##-- resources pageant +PAGERC = pageant.$(RES) ##-- resources pscp SRESRC = scp.$(RES) ##-- resources plink @@ -76,16 +81,19 @@ LRESRC = plink.$(RES) ##-- gui-apps # putty # puttytel +# pageant ##-- console-apps # pscp +# plink ##-- LIBS1 = advapi32.lib user32.lib gdi32.lib LIBS2 = comctl32.lib comdlg32.lib +LIBS3 = shell32.lib SOCK1 = wsock32.lib SOCK2 = ws2_32.lib -all: putty.exe puttytel.exe pscp.exe plink.exe +all: putty.exe puttytel.exe pscp.exe plink.exe pageant.exe putty.exe: $(GOBJS1) $(GOBJS2) $(LOBJS1) $(POBJS) $(MOBJS) $(OBJS1) $(OBJS2) $(OBJS3) $(PRESRC) putty.rsp link $(LFLAGS) -out:putty.exe @putty.rsp @@ -93,6 +101,9 @@ putty.exe: $(GOBJS1) $(GOBJS2) $(LOBJS1) $(POBJS) $(MOBJS) $(OBJS1) $(OBJS2) $(O puttytel.exe: $(GOBJS1) $(GOBJS2) $(LOBJS1) $(TOBJS) $(MOBJS) $(TRESRC) puttytel.rsp link $(LFLAGS) -out:puttytel.exe @puttytel.rsp +pageant.exe: $(PAGE1) $(PAGE2) $(PAGERC) pageant.rsp + link $(LFLAGS) -out:pageant.exe @pageant.rsp + pscp.exe: $(SOBJS) $(MOBJS) $(OBJS1) $(OBJS2) $(OBJS3) $(SRESRC) pscp.rsp link $(LFLAGS) -out:pscp.exe @pscp.rsp @@ -126,6 +137,15 @@ puttytel.rsp: makefile echo $(LIBS2) >> puttytel.rsp echo $(SOCK1) >> puttytel.rsp +pageant.rsp: makefile + echo /nologo /subsystem:windows > pageant.rsp + echo $(PAGE1) >> pageant.rsp + echo $(PAGE2) >> pageant.rsp + echo $(PAGERC) >> pageant.rsp + echo $(LIBS1) >> pageant.rsp + echo $(LIBS2) >> pageant.rsp + echo $(LIBS3) >> pageant.rsp + pscp.rsp: makefile echo /nologo /subsystem:console > pscp.rsp echo $(SOBJS) >> pscp.rsp @@ -181,6 +201,8 @@ be_all.$(OBJ): be_all.c be_nossh.$(OBJ): be_nossh.c be_none.$(OBJ): be_none.c plink.$(OBJ): plink.c putty.h +pageant.$(OBJ): pageant.c ssh.h tree234.h +tree234.$(OBJ): tree234.c tree234.h ##-- # Hack to force version.obj to be rebuilt always @@ -207,6 +229,12 @@ scp.$(RES): scp.rc scp.ico scp.$(RES): rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 scp.rc +##-- dependencies +pageant.$(RES): pageant.rc pageant.ico pageants.ico +##-- +pageant.$(RES): + rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 pageant.rc + clean: del *.obj del *.exe diff --git a/pageant.c b/pageant.c new file mode 100644 index 00000000..d7bd94cf --- /dev/null +++ b/pageant.c @@ -0,0 +1,610 @@ +/* + * Pageant: the PuTTY Authentication Agent. + */ + +#include +#include /* FIXME */ +#include "putty.h" /* FIXME */ +#include "ssh.h" +#include "tree234.h" + +#define IDI_MAINICON 200 +#define IDI_TRAYICON 201 + +#define WM_XUSER (WM_USER + 0x2000) +#define WM_SYSTRAY (WM_XUSER + 6) +#define WM_SYSTRAY2 (WM_XUSER + 7) +#define WM_CLOSEMEM (WM_XUSER + 10) + +#define IDM_CLOSE 0x0010 +#define IDM_VIEWKEYS 0x0020 + +#define APPNAME "Pageant" + +#define MAILSLOTNAME "\\\\.\\mailslot\\pageant_listener" + +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 + +HINSTANCE instance; +HWND hwnd; +HWND keylist; +HMENU systray_menu; + +tree234 *rsakeys; + +/* + * We need this to link with the RSA code, because rsaencrypt() + * pads its data with random bytes. Since we only use rsadecrypt(), + * which is deterministic, this should never be called. + * + * If it _is_ called, there is a _serious_ problem, because it + * won't generate true random numbers. So we must scream, panic, + * and exit immediately if that should happen. + */ +int random_byte(void) { + MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR); + exit(0); +} + +/* + * This function is needed to link with the DES code. We need not + * have it do anything at all. + */ +void logevent(char *msg) { +} + +#define GET_32BIT(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +#define PUT_32BIT(cp, value) { \ + (cp)[0] = (unsigned char)((value) >> 24); \ + (cp)[1] = (unsigned char)((value) >> 16); \ + (cp)[2] = (unsigned char)((value) >> 8); \ + (cp)[3] = (unsigned char)(value); } + +#define PASSPHRASE_MAXLEN 512 + +/* + * Dialog-box function for the passphrase box. + */ +static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + static char *passphrase; + + switch (msg) { + case WM_INITDIALOG: + passphrase = (char *)lParam; + *passphrase = 0; + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (*passphrase) + EndDialog (hwnd, 1); + else + MessageBeep (0); + return 0; + case IDCANCEL: + EndDialog (hwnd, 0); + return 0; + case 102: /* edit box */ + if (HIWORD(wParam) == EN_CHANGE) { + GetDlgItemText (hwnd, 102, passphrase, PASSPHRASE_MAXLEN-1); + passphrase[PASSPHRASE_MAXLEN-1] = '\0'; + } + return 0; + } + return 0; + case WM_CLOSE: + EndDialog (hwnd, 0); + return 0; + } + return 0; +} + +/* + * This function loads a key from a file and adds it. + */ +void add_keyfile(char *filename) { + char passphrase[PASSPHRASE_MAXLEN]; + struct RSAKey *key; + int needs_pass; + int ret; + int attempts; + + needs_pass = rsakey_encrypted(filename); + attempts = 0; + key = malloc(sizeof(*key)); + do { + if (needs_pass) { + int dlgret; + dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210), + NULL, PassphraseProc, + (LPARAM)passphrase); + if (!dlgret) { + free(key); + return; /* operation cancelled */ + } + } else + *passphrase = '\0'; + ret = loadrsakey(filename, key, passphrase); + attempts++; + } while (ret == -1); + if (ret == 0) { + MessageBox(NULL, "Couldn't load public key.", APPNAME, + MB_OK | MB_ICONERROR); + free(key); + return; + } + if (add234(rsakeys, key) != key) + free(key); /* already present, don't waste RAM */ +} + +/* + * This is the main agent function that answers messages. + */ +void answer_msg(void *in, int inlen, void **out, int *outlen) { + unsigned char *ret; + unsigned char *p = in; + int type; + + *out = NULL; /* default `no go' response */ + + /* + * Basic sanity checks. len >= 5, and len[0:4] holds len-4. + */ + if (inlen < 5 || GET_32BIT(p) != (unsigned long)(inlen-4)) + return; + + /* + * Get the message type. + */ + type = p[4]; + + p += 5; + + switch (type) { + case SSH_AGENTC_REQUEST_RSA_IDENTITIES: + /* + * Reply with SSH_AGENT_RSA_IDENTITIES_ANSWER. + */ + { + enum234 e; + struct RSAKey *key; + int len, nkeys; + + /* + * Count up the number and length of keys we hold. + */ + len = nkeys = 0; + for (key = first234(rsakeys, &e); key; key = next234(&e)) { + nkeys++; + len += 4; /* length field */ + len += ssh1_bignum_length(key->exponent); + len += ssh1_bignum_length(key->modulus); + len += 4 + strlen(key->comment); + } + + /* + * Packet header is the obvious five bytes, plus four + * bytes for the key count. + */ + len += 5 + 4; + if ((ret = malloc(len)) != NULL) { + PUT_32BIT(ret, len-4); + ret[4] = SSH_AGENT_RSA_IDENTITIES_ANSWER; + PUT_32BIT(ret+5, nkeys); + p = ret + 5 + 4; + for (key = first234(rsakeys, &e); key; key = next234(&e)) { + PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus)); + p += 4; + p += ssh1_write_bignum(p, key->exponent); + p += ssh1_write_bignum(p, key->modulus); + PUT_32BIT(p, strlen(key->comment)); + memcpy(p+4, key->comment, strlen(key->comment)); + p += 4 + strlen(key->comment); + } + } + } + break; + case SSH_AGENTC_RSA_CHALLENGE: + /* + * Reply with either SSH_AGENT_RSA_RESPONSE or + * SSH_AGENT_FAILURE, depending on whether we have that key + * or not. + */ + { + struct RSAKey reqkey, *key; + Bignum challenge, response; + unsigned char response_source[48], response_md5[16]; + struct MD5Context md5c; + int i, len; + + p += 4; + p += ssh1_read_bignum(p, &reqkey.exponent); + p += ssh1_read_bignum(p, &reqkey.modulus); + p += ssh1_read_bignum(p, &challenge); + memcpy(response_source+32, p, 16); p += 16; + if (GET_32BIT(p) != 1 || + (key = find234(rsakeys, &reqkey, NULL)) == NULL) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); + freebn(challenge); + goto failure; + } + response = rsadecrypt(challenge, key); + for (i = 0; i < 32; i++) + response_source[i] = bignum_byte(response, 31-i); + + MD5Init(&md5c); + MD5Update(&md5c, response_source, 48); + MD5Final(response_md5, &md5c); + memset(response_source, 0, 48); /* burn the evidence */ + freebn(response); /* and that evidence */ + freebn(challenge); /* yes, and that evidence */ + freebn(reqkey.exponent); /* and free some memory ... */ + freebn(reqkey.modulus); /* ... while we're at it. */ + + /* + * Packet is the obvious five byte header, plus sixteen + * bytes of MD5. + */ + len = 5 + 16; + if ((ret = malloc(len)) != NULL) { + PUT_32BIT(ret, len-4); + ret[4] = SSH_AGENT_RSA_RESPONSE; + memcpy(ret+5, response_md5, 16); + } + } + break; +#if 0 /* FIXME: implement these */ + case SSH_AGENTC_ADD_RSA_IDENTITY: + /* + * Add to the list and return SSH_AGENT_SUCCESS, or + * SSH_AGENT_FAILURE if the key was malformed. + */ + break; + case SSH_AGENTC_REMOVE_RSA_IDENTITY: + /* + * Remove from the list and return SSH_AGENT_SUCCESS, or + * perhaps SSH_AGENT_FAILURE if it wasn't in the list to + * start with. + */ + break; +#endif + default: + failure: + /* + * Unrecognised message. Return SSH_AGENT_FAILURE. + */ + if ((ret = malloc(5)) != NULL) { + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + } + break; + } + + if (ret) { + *out = ret; + *outlen = 4 + GET_32BIT(ret); + } +} + +/* + * Key comparison function for the 2-3-4 tree of RSA keys. + */ +int cmpkeys(void *av, void *bv) { + struct RSAKey *a = (struct RSAKey *)av; + struct RSAKey *b = (struct RSAKey *)bv; + Bignum am, bm; + int alen, blen; + + am = a->modulus; + bm = b->modulus; + /* + * Compare by length of moduli. + */ + alen = ssh1_bignum_bitcount(am); + blen = ssh1_bignum_bitcount(bm); + if (alen > blen) return +1; else if (alen < blen) return -1; + /* + * Now compare by moduli themselves. + */ + alen = (alen + 7) / 8; /* byte count */ + while (alen-- > 0) { + int abyte, bbyte; + abyte = bignum_byte(am, alen); + bbyte = bignum_byte(bm, alen); + if (abyte > bbyte) return +1; else if (abyte < bbyte) return -1; + } + /* + * Give up. + */ + return 0; +} + +static void error(char *s) { + MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR); +} + +/* + * Dialog-box function for the key list box. + */ +static int CALLBACK KeyListProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + enum234 e; + struct RSAKey *key; + OPENFILENAME of; + char filename[FILENAME_MAX]; + + switch (msg) { + case WM_INITDIALOG: + for (key = first234(rsakeys, &e); key; key = next234(&e)) { + SendDlgItemMessage (hwnd, 100, LB_ADDSTRING, + 0, (LPARAM) key->comment); + } + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + keylist = NULL; + DestroyWindow(hwnd); + return 0; + case 101: /* add key */ + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + memset(&of, 0, sizeof(of)); +#ifdef OPENFILENAME_SIZE_VERSION_400 + of.lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + of.lStructSize = sizeof(of); +#endif + of.hwndOwner = hwnd; + of.lpstrFilter = "All Files\0*\0\0\0"; + of.lpstrCustomFilter = NULL; + of.nFilterIndex = 1; + of.lpstrFile = filename; *filename = '\0'; + of.nMaxFile = sizeof(filename); + of.lpstrFileTitle = NULL; + of.lpstrInitialDir = NULL; + of.lpstrTitle = "Select Public Key File"; + of.Flags = 0; + if (GetOpenFileName(&of)) { + add_keyfile(filename); + } + SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0); + for (key = first234(rsakeys, &e); key; key = next234(&e)) { + SendDlgItemMessage (hwnd, 100, LB_ADDSTRING, + 0, (LPARAM) key->comment); + } + SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0); + } + return 0; + case 102: /* remove key */ + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0); + if (n == LB_ERR || n == 0) { + MessageBeep(0); + break; + } + for (key = first234(rsakeys, &e); key; key = next234(&e)) + if (n-- == 0) + break; + del234(rsakeys, key); + freersakey(key); free(key); + SendDlgItemMessage(hwnd, 100, LB_RESETCONTENT, 0, 0); + for (key = first234(rsakeys, &e); key; key = next234(&e)) { + SendDlgItemMessage (hwnd, 100, LB_ADDSTRING, + 0, (LPARAM) key->comment); + } + SendDlgItemMessage (hwnd, 100, LB_SETCURSEL, (WPARAM) -1, 0); + } + return 0; + } + return 0; + case WM_CLOSE: + keylist = NULL; + DestroyWindow(hwnd); + return 0; + } + return 0; +} + +static LRESULT CALLBACK WndProc (HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) { + int ret; + static int menuinprogress; + + switch (message) { + case WM_SYSTRAY: + if (lParam == WM_RBUTTONUP) { + POINT cursorpos; + GetCursorPos(&cursorpos); + PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y); + } + break; + case WM_SYSTRAY2: + if (!menuinprogress) { + menuinprogress = 1; + SetForegroundWindow(hwnd); + ret = TrackPopupMenu(systray_menu, + TPM_RIGHTALIGN | TPM_BOTTOMALIGN | + TPM_RIGHTBUTTON, + wParam, lParam, 0, hwnd, NULL); + menuinprogress = 0; + } + break; + case WM_COMMAND: + case WM_SYSCOMMAND: + switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ + case IDM_CLOSE: + SendMessage(hwnd, WM_CLOSE, 0, 0); + break; + case IDM_VIEWKEYS: + if (!keylist) { + keylist = CreateDialog (instance, MAKEINTRESOURCE(211), + NULL, KeyListProc); + ShowWindow (keylist, SW_SHOWNORMAL); + } + break; + } + break; + case WM_DESTROY: + PostQuitMessage (0); + return 0; + case WM_COPYDATA: + { + COPYDATASTRUCT *cds; + void *in, *out, *ret; + int inlen, outlen; + HANDLE filemap; + char mapname[64]; + int id; + + cds = (COPYDATASTRUCT *)lParam; + /* + * FIXME: use dwData somehow. + */ + in = cds->lpData; + inlen = cds->cbData; + answer_msg(in, inlen, &out, &outlen); + if (out) { + id = 0; + do { + sprintf(mapname, "PageantReply%08x", ++id); + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, + NULL, PAGE_READWRITE, + 0, outlen+sizeof(int), + mapname); + } while (filemap == INVALID_HANDLE_VALUE); + ret = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, + outlen+sizeof(int)); + if (ret) { + *((int *)ret) = outlen; + memcpy(((int *)ret)+1, out, outlen); + UnmapViewOfFile(ret); + return id; + } + } else + return 0; /* invalid request */ + } + break; + case WM_CLOSEMEM: + /* + * FIXME! + */ + break; + } + + return DefWindowProc (hwnd, message, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { + WNDCLASS wndclass; + HANDLE mailslot; + MSG msg; + + instance = inst; + + if (!prev) { + wndclass.style = 0; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon (inst, + MAKEINTRESOURCE(IDI_MAINICON)); + wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM); + wndclass.hbrBackground = GetStockObject (BLACK_BRUSH); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = APPNAME; + + RegisterClass (&wndclass); + } + + hwnd = keylist = NULL; + + hwnd = CreateWindow (APPNAME, APPNAME, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + + /* Set up a system tray icon */ + { + BOOL res; + NOTIFYICONDATA tnid; + HICON hicon; + +#ifdef NIM_SETVERSION + tnid.uVersion = 0; + res = Shell_NotifyIcon(NIM_SETVERSION, &tnid); +#endif + + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = hwnd; + tnid.uID = 1; /* unique within this systray use */ + tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + tnid.uCallbackMessage = WM_SYSTRAY; + tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201)); + strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); + + res = Shell_NotifyIcon(NIM_ADD, &tnid); + + if (hicon) + DestroyIcon(hicon); + + systray_menu = CreatePopupMenu(); + AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "View Keys"); + AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "Terminate"); + } + + ShowWindow (hwnd, SW_HIDE); + + /* + * Create the mailslot. + */ + { + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + mailslot = CreateMailslot(MAILSLOTNAME, 0, 0, &sa); + } + + /* + * Initialise storage for RSA keys. + */ + rsakeys = newtree234(cmpkeys); + + while (GetMessage(&msg, NULL, 0, 0) == 1) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* Clean up the system tray icon */ + { + NOTIFYICONDATA tnid; + + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = hwnd; + tnid.uID = 1; + + Shell_NotifyIcon(NIM_DELETE, &tnid); + + DestroyMenu(systray_menu); + } + + exit(msg.wParam); +} diff --git a/pageant.ico b/pageant.ico new file mode 100644 index 0000000000000000000000000000000000000000..ba19e0a10046d9c7aacfb4ea123192541abc217f GIT binary patch literal 1078 zcmc(eF>=B%5JkUnW=xTZxdE;++#)$ZZoviU><)Ff%azon;SiK5)8WE{@>Vk7Oh^XO zWbLor{VT6z`CG~~QB}=k_CApw@v~XV7n$^sOAELPJcCSkjFMySWpfm%IF}qFp}hBa zD};C}1d9NdQ8wIc?a5a0*?n!+NSk()8`nA`SY@y#=7o`F)6OM7G<2^iFVpmWYA2QV zTeG)+%i}Ipq}{QfxWA9Y+E~}=B+kqgQ5ZFdhcwQ^*Q&}Z8vbp{@!PQadM7=BIVcTq zWe0jVu>J*0YB?wm^e}04%7Y&eb~!IJpu=~|5c;(8LTDdU;HYy?eAyftr-G)VV +#endif + +200 ICON "pageant.ico" +201 ICON "pageants.ico" + +210 DIALOG DISCARDABLE 0, 0, 140, 60 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pageant: Enter Passphrase" +FONT 8, "MS Sans Serif" +BEGIN + CTEXT "Enter passphrase for key", 100, 10, 6, 120, 8 + CTEXT "", 101, 10, 16, 120, 8 + EDITTEXT 102, 10, 26, 120, 12, ES_PASSWORD + DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 + PUSHBUTTON "&Cancel", IDCANCEL, 80, 42, 40, 14 +END + +211 DIALOG DISCARDABLE 0, 0, 300, 200 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pageant Key List" +FONT 8, "MS Sans Serif" +BEGIN + LISTBOX 100, 10, 10, 280, 155, + LBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Add Key", 101, 60, 162, 60, 14 + PUSHBUTTON "&Remove Key", 102, 180, 162, 60, 14 + DEFPUSHBUTTON "&Close", IDOK, 240, 182, 50, 14 +END diff --git a/pageantc.c b/pageantc.c new file mode 100644 index 00000000..e718313e --- /dev/null +++ b/pageantc.c @@ -0,0 +1,167 @@ +/* + * Pageant client code. + */ + +#include +#include +#include + +#ifdef TESTMODE +#define debug(x) (printf x) +#else +#define debug(x) +#endif + +int agent_exists(void) { + HWND hwnd; + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return FALSE; + else + return TRUE; +} + +void agent_query(void *in, int inlen, void **out, int *outlen) { +#if 0 +#define MAILSLOTNAME "\\\\.\\mailslot\\pageant_listener" + SECURITY_ATTRIBUTES sa; + HANDLE my_mailslot, agent_mailslot; + char name[64]; + char *p; + DWORD msglen, byteswritten, bytesread, inid; + + *out = NULL; + *outlen = 0; + + agent_mailslot = CreateFile(MAILSLOTNAME, GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + (HANDLE)NULL); + debug(("opened %s: %p\n", MAILSLOTNAME, agent_mailslot)); + if (agent_mailslot == INVALID_HANDLE_VALUE) + return; + + inid = GetCurrentThreadId(); + inid--; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + do { + sprintf(name, "\\\\.\\mailslot\\pclient_request_%08x", ++inid); + /* + * Five-minute timeout. + */ + my_mailslot = CreateMailslot(name, 0, 0, &sa); + debug(("mailslot %s: %p\n", name, my_mailslot)); + } while (my_mailslot == INVALID_HANDLE_VALUE); + Sleep(3000); + + msglen = strlen(name) + 1 + inlen; + p = malloc(msglen); + if (!p) { + CloseHandle(my_mailslot); + CloseHandle(agent_mailslot); + return; + } + + strcpy(p, name); + memcpy(p+strlen(p)+1, in, inlen); + + debug(("ooh\n")); + if (WriteFile(agent_mailslot, p, msglen, &byteswritten, NULL) == 0) { + debug(("eek!\n")); + free(p); + CloseHandle(my_mailslot); + CloseHandle(agent_mailslot); + return; + } + debug(("aah\n")); + free(p); + CloseHandle(agent_mailslot); + + WaitForSingleObject(my_mailslot, 3000000); + debug(("waited\n")); + if (!GetMailslotInfo(my_mailslot, NULL, &msglen, NULL, NULL)) { + CloseHandle(my_mailslot); + return; + } + if (msglen == MAILSLOT_NO_MESSAGE) { + debug(("no message\n")); + CloseHandle(my_mailslot); + return; + } + debug(("msglen=%d\n", msglen)); + p = malloc(msglen); + if (!p) { + CloseHandle(my_mailslot); + return; + } + if (ReadFile(my_mailslot, p, msglen, &bytesread, NULL) == 0 && + bytesread == msglen) { + *out = p; + *outlen = msglen; + } + CloseHandle(my_mailslot); +#endif + HWND hwnd; + char mapname[64]; + HANDLE filemap; + void *p, *ret; + int id, retlen; + COPYDATASTRUCT cds; + + *out = NULL; + *outlen = 0; + + hwnd = FindWindow("Pageant", "Pageant"); + debug(("hwnd is %p\n", hwnd)); + if (!hwnd) + return; + cds.dwData = 0; /* FIXME */ + cds.cbData = inlen; + cds.lpData = in; + id = SendMessage(hwnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds); + debug(("return is %d\n", id)); + if (id > 0) { + sprintf(mapname, "PageantReply%08x", id); + filemap = OpenFileMapping(FILE_MAP_READ, FALSE, mapname); + debug(("name is `%s', filemap is %p\n", mapname, filemap)); + debug(("error is %d\n", GetLastError())); + if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) { + p = MapViewOfFile(filemap, FILE_MAP_READ, 0, 0, 0); + debug(("p is %p\n", p)); + if (p) { + retlen = *(int *)p; + debug(("len is %d\n", retlen)); + ret = malloc(retlen); + if (ret) { + memcpy(ret, ((int *)p) + 1, retlen); + *out = ret; + *outlen = retlen; + } + UnmapViewOfFile(p); + } + CloseHandle(filemap); + } + /* FIXME: tell agent to close its handle too */ + } +} + +#ifdef TESTMODE + +int main(void) { + void *msg; + int len; + int i; + + agent_query("\0\0\0\1\1", 5, &msg, &len); + debug(("%d:", len)); + for (i = 0; i < len; i++) + debug((" %02x", ((unsigned char *)msg)[i])); + debug(("\n")); + return 0; +} + +#endif diff --git a/pageants.ico b/pageants.ico new file mode 100644 index 0000000000000000000000000000000000000000..b563925bb6927a8ee9caf6274ab697a3053e0d8d GIT binary patch literal 318 zcmZ{gF%H5&3UsNQXN@)U9Dk4x1>ikrzD?qws`o!IV$vKa=B8~PJX>bGIpQmYL n{#RLJypExnYhE|M>ez914>k>f27Uqm5#ZsSL&Fm_UY!31r++#D literal 0 HcmV?d00001 diff --git a/putty.h b/putty.h index 15054779..6143643b 100644 --- a/putty.h +++ b/putty.h @@ -355,6 +355,12 @@ void crypto_wrapup(); #endif /* + * Exports from pageantc.c + */ +void agent_query(void *in, int inlen, void **out, int *outlen); +int agent_exists(void); + +/* * A debug system. */ #ifdef DEBUG diff --git a/ssh.c b/ssh.c index 41d0cf02..dd8ffd57 100644 --- a/ssh.c +++ b/ssh.c @@ -51,6 +51,15 @@ #define SSH1_AUTH_TIS 5 #define SSH1_AUTH_CCARD 16 +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 + #define SSH2_MSG_DISCONNECT 1 #define SSH2_MSG_IGNORE 2 #define SSH2_MSG_UNIMPLEMENTED 3 @@ -522,7 +531,6 @@ static void send_packet(int pkttype, ...) unsigned long argint; int pktlen, argtype, arglen; Bignum bn; - int i; pktlen = 0; va_start(args, pkttype); @@ -548,10 +556,7 @@ static void send_packet(int pkttype, ...) break; case PKT_BIGNUM: bn = va_arg(args, Bignum); - i = 16 * bn[0] - 1; - while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 ) - i--; - pktlen += 2 + (i+7)/8; + pktlen += ssh1_bignum_length(bn); break; default: assert(0); @@ -590,18 +595,7 @@ static void send_packet(int pkttype, ...) break; case PKT_BIGNUM: bn = va_arg(args, Bignum); - i = 16 * bn[0] - 1; - while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 ) - i--; - *p++ = (i >> 8) & 0xFF; - *p++ = i & 0xFF; - i = (i + 7) / 8; - while (i-- > 0) { - if (i % 2) - *p++ = bn[i/2+1] >> 8; - else - *p++ = bn[i/2+1] & 0xFF; - } + p += ssh1_write_bignum(p, bn); break; } } @@ -1139,7 +1133,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static char username[100]; static int pos = 0; static char c; - if (!(flags & FLAG_CONNECTION) && !*cfg.username) { + if ((flags & FLAG_CONNECTION) && !*cfg.username) { c_write("login as: ", 10); while (pos >= 0) { crWaitUntil(!ispkt); @@ -1208,6 +1202,98 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) * authentication. */ pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + if (agent_exists()) { + /* + * Attempt RSA authentication using Pageant. + */ + static unsigned char request[5], *response, *p; + static int responselen; + static int i, nkeys; + static int authed = FALSE; + void *r; + + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(request, 1); + request[4] = SSH_AGENTC_REQUEST_RSA_IDENTITIES; + agent_query(request, 5, &r, &responselen); + response = (unsigned char *)r; + if (response) { + p = response + 5; + nkeys = GET_32BIT(p); p += 4; + { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys); + logevent(buf); } + for (i = 0; i < nkeys; i++) { + static struct RSAKey key; + static Bignum challenge; + + { char buf[64]; sprintf(buf, "Trying Pageant key #%d", i); + logevent(buf); } + p += 4; + p += ssh1_read_bignum(p, &key.exponent); + p += ssh1_read_bignum(p, &key.modulus); + send_packet(SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, key.modulus, PKT_END); + crWaitUntil(ispkt); + if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + logevent("Key refused"); + continue; + } + logevent("Received RSA challenge"); + ssh1_read_bignum(pktin.body, &challenge); + { + char *agentreq, *q, *ret; + int len, retlen; + len = 1 + 4; /* message type, bit count */ + len += ssh1_bignum_length(key.exponent); + len += ssh1_bignum_length(key.modulus); + len += ssh1_bignum_length(challenge); + len += 16; /* session id */ + len += 4; /* response format */ + agentreq = malloc(4 + len); + PUT_32BIT(agentreq, len); + q = agentreq + 4; + *q++ = SSH_AGENTC_RSA_CHALLENGE; + PUT_32BIT(q, ssh1_bignum_bitcount(key.modulus)); + q += 4; + q += ssh1_write_bignum(q, key.exponent); + q += ssh1_write_bignum(q, key.modulus); + q += ssh1_write_bignum(q, challenge); + memcpy(q, session_id, 16); q += 16; + PUT_32BIT(q, 1); /* response format */ + agent_query(agentreq, len+4, &ret, &retlen); + free(agentreq); + if (ret) { + if (ret[4] == SSH_AGENT_RSA_RESPONSE) { + logevent("Sending Pageant's response"); + send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, ret+5, 16, PKT_END); + free(ret); + crWaitUntil(ispkt); + if (pktin.type == SSH1_SMSG_SUCCESS) { + logevent("Pageant's response accepted"); + authed = TRUE; + } else + logevent("Pageant's response not accepted"); + } else { + logevent("Pageant failed to answer challenge"); + free(ret); + } + } else { + logevent("No reply received from Pageant"); + } + } + freebn(key.exponent); + freebn(key.modulus); + freebn(challenge); + if (authed) + break; + } + } + if (authed) + break; + } if (*cfg.keyfile && !tried_publickey) pwpkt_type = SSH1_CMSG_AUTH_RSA; diff --git a/ssh.h b/ssh.h index c3233fe9..a2b64012 100644 --- a/ssh.h +++ b/ssh.h @@ -36,6 +36,7 @@ struct RSAKey { Bignum exponent; Bignum private_exponent; #endif + char *comment; }; int makekey(unsigned char *data, struct RSAKey *result, @@ -47,6 +48,7 @@ void rsasign(unsigned char *data, int length, struct RSAKey *key); void rsasanitise(struct RSAKey *key); int rsastr_len(struct RSAKey *key); void rsastr_fmt(char *str, struct RSAKey *key); +void freersakey(struct RSAKey *key); typedef unsigned int word32; typedef unsigned int uint32; @@ -145,6 +147,10 @@ void modmul(Bignum a, Bignum b, Bignum mod, Bignum result); void decbn(Bignum n); extern Bignum Zero, One; int ssh1_read_bignum(unsigned char *data, Bignum *result); +int ssh1_bignum_bitcount(Bignum bn); +int ssh1_bignum_length(Bignum bn); +int bignum_byte(Bignum bn, int i); +int ssh1_write_bignum(void *data, Bignum bn); Bignum dh_create_e(void); Bignum dh_find_K(Bignum f); @@ -153,4 +159,4 @@ int loadrsakey(char *filename, struct RSAKey *key, char *passphrase); int rsakey_encrypted(char *filename); void des3_decrypt_pubkey(unsigned char *key, - unsigned char *blk, int len); \ No newline at end of file + unsigned char *blk, int len); diff --git a/sshbn.c b/sshbn.c index 97ae3575..adb88243 100644 --- a/sshbn.c +++ b/sshbn.c @@ -340,3 +340,50 @@ int ssh1_read_bignum(unsigned char *data, Bignum *result) { return p - data; } + +/* + * Return the bit count of a bignum, for ssh1 encoding. + */ +int ssh1_bignum_bitcount(Bignum bn) { + int bitcount = bn[0] * 16 - 1; + + while (bitcount >= 0 && (bn[bitcount/16+1] >> (bitcount % 16)) == 0) + bitcount--; + return bitcount + 1; +} + +/* + * Return the byte length of a bignum when ssh1 encoded. + */ +int ssh1_bignum_length(Bignum bn) { + return 2 + (ssh1_bignum_bitcount(bn)+7)/8; +} + +/* + * Return a byte from a bignum; 0 is least significant, etc. + */ +int bignum_byte(Bignum bn, int i) { + if (i >= 2*bn[0]) + return 0; /* beyond the end */ + else if (i & 1) + return (bn[i/2+1] >> 8) & 0xFF; + else + return (bn[i/2+1] ) & 0xFF; +} + +/* + * Write a ssh1-format bignum into a buffer. It is assumed the + * buffer is big enough. Returns the number of bytes used. + */ +int ssh1_write_bignum(void *data, Bignum bn) { + unsigned char *p = data; + int len = ssh1_bignum_length(bn); + int i; + int bitc = ssh1_bignum_bitcount(bn); + + *p++ = (bitc >> 8) & 0xFF; + *p++ = (bitc ) & 0xFF; + for (i = len-2; i-- ;) + *p++ = bignum_byte(bn, i); + return len; +} diff --git a/sshpubk.c b/sshpubk.c index da22ee6d..3e4de1fc 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -67,10 +67,14 @@ int loadrsakey(char *filename, struct RSAKey *key, char *passphrase) { /* Next, the comment field. */ j = GET_32BIT(buf+i); - if (len-i < 4+j) goto end; i += 4+j; - /* - * FIXME: might need to use this string. - */ + i += 4; + if (len-i < j) goto end; + key->comment = malloc(j+1); + if (key->comment) { + memcpy(key->comment, buf+i, j); + key->comment[j] = '\0'; + } + i += j; /* * Decrypt remainder of buffer. @@ -80,7 +84,7 @@ int loadrsakey(char *filename, struct RSAKey *key, char *passphrase) { MD5Update(&md5c, passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); des3_decrypt_pubkey(keybuf, buf+i, (len-i+7)&~7); - memset(keybuf, 0, sizeof(buf)); /* burn the evidence */ + memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ } /* diff --git a/sshrsa.c b/sshrsa.c index bd92c7bd..e44dce80 100644 --- a/sshrsa.c +++ b/sshrsa.c @@ -156,6 +156,13 @@ void rsastr_fmt(char *str, struct RSAKey *key) { str[len] = '\0'; } +void freersakey(struct RSAKey *key) { + if (key->modulus) freebn(key->modulus); + if (key->exponent) freebn(key->exponent); + if (key->private_exponent) freebn(key->private_exponent); + if (key->comment) free(key->comment); +} + #ifdef TESTMODE #ifndef NODDY -- 2.11.0