From 6e522441172d5b1c2a2fa4d0f6bbe905ce6b647a Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 19 Oct 2000 15:43:08 +0000 Subject: [PATCH] PuTTYgen initial version. Still to do are basic user-friendliness features (prompt for passphrase twice, prompt before overwriting a file, check the key file was actually saved OK), testing of the generated keys to make sure I got the file format right, and support for a variable key size. I think what's already here is basically sound though. git-svn-id: svn://svn.tartarus.org/sgt/putty@715 cda61777-01e9-0310-a592-d414129be87e --- Makefile | 36 +++- pageant.c | 2 +- puttygen.c | 663 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ puttygen.rc | 73 +++++++ ssh.c | 2 +- ssh.h | 10 +- sshbn.c | 86 +++++++- sshdes.c | 12 ++ sshpubk.c | 122 ++++++++++- sshrand.c | 90 ++++++--- winctrls.c | 71 ++++++- winstuff.h | 6 + 12 files changed, 1117 insertions(+), 56 deletions(-) create mode 100644 puttygen.c create mode 100644 puttygen.rc diff --git a/Makefile b/Makefile index 8c1fb9a5..4e20f611 100644 --- a/Makefile +++ b/Makefile @@ -72,10 +72,17 @@ OBJS3 = sshbn.$(OBJ) sshpubk.$(OBJ) ssh.$(OBJ) pageantc.$(OBJ) tree234.$(OBJ) ##-- objects pageant PAGE1 = pageant.$(OBJ) sshrsa.$(OBJ) sshpubk.$(OBJ) sshdes.$(OBJ) sshbn.$(OBJ) PAGE2 = sshmd5.$(OBJ) version.$(OBJ) tree234.$(OBJ) +##-- objects puttygen +GEN1 = puttygen.$(OBJ) sshrsag.$(OBJ) sshprime.$(OBJ) sshdes.$(OBJ) +GEN2 = sshbn.$(OBJ) sshmd5.$(OBJ) version.$(OBJ) sshrand.$(OBJ) noise.$(OBJ) +GEN3 = sshsha.$(OBJ) winstore.$(OBJ) misc.$(OBJ) winctrls.$(OBJ) +GEN4 = sshrsa.$(OBJ) sshpubk.$(OBJ) ##-- resources putty puttytel PRESRC = win_res.$(RES) ##-- resources pageant PAGERC = pageant.$(RES) +##-- resources puttygen +GENRC = puttygen.$(RES) ##-- resources pscp SRESRC = scp.$(RES) ##-- resources plink @@ -86,6 +93,7 @@ LRESRC = plink.$(RES) # putty # puttytel # pageant +# puttygen ##-- console-apps # pscp # plink ws2_32 @@ -97,7 +105,7 @@ LIBS3 = shell32.lib SOCK1 = wsock32.lib SOCK2 = ws2_32.lib -all: putty.exe puttytel.exe pscp.exe plink.exe pageant.exe +all: putty.exe puttytel.exe pscp.exe plink.exe pageant.exe puttygen.exe putty.exe: $(GOBJS1) $(GOBJS2) $(LOBJS1) $(POBJS) $(MOBJS) $(OBJS1) $(OBJS2) $(OBJS3) $(PRESRC) putty.rsp link $(LFLAGS) -out:putty.exe @putty.rsp @@ -108,6 +116,9 @@ puttytel.exe: $(GOBJS1) $(GOBJS2) $(LOBJS1) $(TOBJS) $(MOBJS) $(PRESRC) puttytel pageant.exe: $(PAGE1) $(PAGE2) $(PAGERC) pageant.rsp link $(LFLAGS) -out:pageant.exe @pageant.rsp +puttygen.exe: $(GEN1) $(GEN2) $(GEN3) $(GEN4) $(GENRC) puttygen.rsp + link $(LFLAGS) -out:puttygen.exe @puttygen.rsp + pscp.exe: $(SOBJS) $(MOBJS) $(OBJS1) $(OBJS2) $(OBJS3) $(SRESRC) pscp.rsp link $(LFLAGS) -out:pscp.exe @pscp.rsp @@ -143,13 +154,24 @@ puttytel.rsp: makefile pageant.rsp: makefile echo /nologo /subsystem:windows > pageant.rsp - echo $(PAGE1) >> pageant.rsp - echo $(PAGE2) >> pageant.rsp - echo $(PAGERC) >> 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 +puttygen.rsp: makefile + echo /nologo /subsystem:windows > puttygen.rsp + echo $(GEN1) >> puttygen.rsp + echo $(GEN2) >> puttygen.rsp + echo $(GEN3) >> puttygen.rsp + echo $(GEN4) >> puttygen.rsp + echo $(GENRC) >> puttygen.rsp + echo $(LIBS1) >> puttygen.rsp + echo $(LIBS2) >> puttygen.rsp + echo $(LIBS3) >> puttygen.rsp + pscp.rsp: makefile echo /nologo /subsystem:console > pscp.rsp echo $(SOBJS) >> pscp.rsp @@ -236,6 +258,12 @@ pageant.$(RES): pageant.rc pageant.ico pageants.ico pageant.$(RES): rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 pageant.rc +##-- dependencies +puttygen.$(RES): puttygen.rc puttygen.ico +##-- +puttygen.$(RES): + rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 puttygen.rc + clean: del *.obj del *.exe diff --git a/pageant.c b/pageant.c index ac0823df..8806f13b 100644 --- a/pageant.c +++ b/pageant.c @@ -252,7 +252,7 @@ static void add_keyfile(char *filename) { } } else *passphrase = '\0'; - ret = loadrsakey(filename, key, passphrase); + ret = loadrsakey(filename, key, NULL, passphrase); attempts++; } while (ret == -1); if (comment) free(comment); diff --git a/puttygen.c b/puttygen.c new file mode 100644 index 00000000..c633f5af --- /dev/null +++ b/puttygen.c @@ -0,0 +1,663 @@ +/* + * PuTTY key generation front end. + */ + +#include +#include +#include +#ifndef NO_SECURITY +#include +#endif +#include + +#define PUTTY_DO_GLOBALS + +#include "putty.h" +#include "ssh.h" +#include "winstuff.h" + +#define WM_DONEKEY (WM_XUSER + 1) + +#define KEYSIZE 1024 + +/* + * TODO: + * - have some means of verifying passphrase changes against typos + * - prompt before overwriting an existing file + * - check the return value from saversakey() + * - test the generated keys for actual working-RSA-key-hood + * - variable key size + */ + +/* ---------------------------------------------------------------------- + * Progress report code. This is really horrible :-) + */ +#define PHASE1TOTAL 0x10000 +#define PHASE2TOTAL 0x10000 +#define PHASE3TOTAL 0x04000 +#define PHASE1START 0 +#define PHASE2START (PHASE1TOTAL) +#define PHASE3START (PHASE1TOTAL + PHASE2TOTAL) +#define TOTALTOTAL (PHASE1TOTAL + PHASE2TOTAL + PHASE3TOTAL) +#define PROGRESSBIGRANGE 65535 +#define DIVISOR ((TOTALTOTAL + PROGRESSBIGRANGE - 1) / PROGRESSBIGRANGE) +#define PROGRESSRANGE (TOTALTOTAL / DIVISOR) +struct progress { + unsigned phase1param, phase1current, phase1n; + unsigned phase2param, phase2current, phase2n; + unsigned phase3mult; + HWND progbar; +}; + +static void progress_update(void *param, int phase, int iprogress) { + struct progress *p = (struct progress *)param; + unsigned progress = iprogress; + int position; + + switch (phase) { + case -1: + p->phase1param = 0x10000 + progress; + p->phase1current = 0x10000; p->phase1n = 0; + return; + case -2: + p->phase2param = 0x10000 + progress; + p->phase2current = 0x10000; p->phase2n = 0; + return; + case -3: + p->phase3mult = PHASE3TOTAL / progress; + return; + case 1: + while (p->phase1n < progress) { + p->phase1n++; + p->phase1current *= p->phase1param; + p->phase1current /= 0x10000; + } + position = PHASE1START + 0x10000 - p->phase1current; + break; + case 2: + while (p->phase2n < progress) { + p->phase2n++; + p->phase2current *= p->phase2param; + p->phase2current /= 0x10000; + } + position = PHASE2START + 0x10000 - p->phase2current; + break; + case 3: + position = PHASE3START + progress * p->phase3mult; + break; + } + + SendMessage(p->progbar, PBM_SETPOS, position / DIVISOR, 0); +} + +extern char ver[]; + +#define PASSPHRASE_MAXLEN 512 + +struct PassphraseProcStruct { + char *passphrase; + char *comment; +}; + +/* + * Dialog-box function for the passphrase box. + */ +static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + static char *passphrase; + struct PassphraseProcStruct *p; + + switch (msg) { + case WM_INITDIALOG: + SetForegroundWindow(hwnd); + SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + p = (struct PassphraseProcStruct *)lParam; + passphrase = p->passphrase; + if (p->comment) + SetDlgItemText(hwnd, 101, p->comment); + *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; +} + +/* + * Prompt for a key file. Assumes the filename buffer is of size + * FILENAME_MAX. + */ +static int prompt_keyfile(HWND hwnd, char *dlgtitle, + char *filename, int save) { + OPENFILENAME of; + 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 = FILENAME_MAX; + of.lpstrFileTitle = NULL; + of.lpstrInitialDir = NULL; + of.lpstrTitle = dlgtitle; + of.Flags = 0; + if (save) + return GetSaveFileName(&of); + else + return GetOpenFileName(&of); +} + +/* + * This function is needed to link with the DES code. We need not + * have it do anything at all. + */ +void logevent(char *msg) { +} + +/* + * Dialog-box function for the Licence box. + */ +static int CALLBACK LicenceProc (HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_INITDIALOG: + return 1; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 1); + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 1); + return 0; + } + return 0; +} + +/* + * Dialog-box function for the About box. + */ +static int CALLBACK AboutProc (HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_INITDIALOG: + SetDlgItemText (hwnd, 100, ver); + return 1; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 1); + return 0; + case 101: + EnableWindow(hwnd, 0); + DialogBox (hinst, MAKEINTRESOURCE(214), NULL, LicenceProc); + EnableWindow(hwnd, 1); + SetActiveWindow(hwnd); + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 1); + return 0; + } + return 0; +} + +/* + * Thread to generate a key. + */ +struct rsa_key_thread_params { + HWND progressbar; /* notify this with progress */ + HWND dialog; /* notify this on completion */ + struct RSAKey *key; + struct RSAAux *aux; +}; +static DWORD WINAPI generate_rsa_key_thread(void *param) { + struct rsa_key_thread_params *params = + (struct rsa_key_thread_params *)param; + struct progress prog; + prog.progbar = params->progressbar; + + rsa_generate(params->key, params->aux, KEYSIZE, progress_update, &prog); + + PostMessage(params->dialog, WM_DONEKEY, 0, 0); + + free(params); + return 0; +} + +struct MainDlgState { + int collecting_entropy; + int generation_thread_exists; + int key_exists; + int entropy_got, entropy_required, entropy_size; + unsigned *entropy; + struct RSAKey key; + struct RSAAux aux; +}; + +static void hidemany(HWND hwnd, const int *ids, int hideit) { + while (*ids) { + ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); + } +} + +static void setupbigedit(HWND hwnd, int id, struct RSAKey *key) { + char *buffer; + char *dec1, *dec2; + + dec1 = bignum_decimal(key->exponent); + dec2 = bignum_decimal(key->modulus); + buffer = malloc(strlen(dec1)+strlen(dec2)+ + strlen(key->comment)+30); + sprintf(buffer, "%d %s %s %s", + ssh1_bignum_bitcount(key->modulus), + dec1, dec2, key->comment); + SetDlgItemText(hwnd, id, buffer); + free(dec1); + free(dec2); + free(buffer); +} + +/* + * Dialog-box function for the main PuTTYgen dialog box. + */ +static int CALLBACK MainDlgProc (HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + enum { + controlidstart = 100, + IDC_TITLE, + IDC_BOX_KEY, IDC_BOXT_KEY, + IDC_NOKEY, + IDC_GENERATING, + IDC_PROGRESS, + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_FPSTATIC, IDC_FINGERPRINT, + IDC_COMMENTSTATIC, IDC_COMMENTEDIT, + IDC_PASSPHRASESTATIC, IDC_PASSPHRASEEDIT, + IDC_BOX_ACTIONS, IDC_BOXT_ACTIONS, + IDC_GENSTATIC, IDC_GENERATE, + IDC_LOADSTATIC, IDC_LOAD, + IDC_SAVESTATIC, IDC_SAVE, + IDC_ABOUT, + }; + static const int nokey_ids[] = { IDC_NOKEY, 0 }; + static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 }; + static const int gotkey_ids[] = { + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_FPSTATIC, IDC_FINGERPRINT, + IDC_COMMENTSTATIC, IDC_COMMENTEDIT, + IDC_PASSPHRASESTATIC, IDC_PASSPHRASEEDIT, 0 }; + static const char generating_msg[] = + "Please wait while a key is generated..."; + static const char entropy_msg[] = + "Please move the mouse in this window to generate randomness"; + struct MainDlgState *state; + + switch (msg) { + case WM_INITDIALOG: + state = malloc(sizeof(*state)); + state->generation_thread_exists = FALSE; + state->key_exists = FALSE; + SetWindowLong(hwnd, GWL_USERDATA, (LONG)state); + { + struct ctlpos cp, cp2; + + ctlposinit(&cp, hwnd, 10, 10, 10); + bartitle(&cp, "Public and private key generation for PuTTY", + IDC_TITLE); + beginbox(&cp, "Key", + IDC_BOX_KEY, IDC_BOXT_KEY); + cp2 = cp; + statictext(&cp2, "No key.", IDC_NOKEY); + cp2 = cp; + statictext(&cp2, "", + IDC_GENERATING); + progressbar(&cp2, IDC_PROGRESS); + bigeditctrl(&cp, + "&Public key for pasting into authorized_keys file:", + IDC_PKSTATIC, IDC_KEYDISPLAY, 7); + SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0); + staticedit(&cp, "Key fingerprint:", IDC_FPSTATIC, + IDC_FINGERPRINT, 70); + SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1, 0); + staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC, + IDC_COMMENTEDIT, 70); + staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASESTATIC, + IDC_PASSPHRASEEDIT, 70); + endbox(&cp); + beginbox(&cp, "Actions", + IDC_BOX_ACTIONS, IDC_BOXT_ACTIONS); + staticbtn(&cp, "Generate a public/private key pair", + IDC_GENSTATIC, "&Generate", IDC_GENERATE); + staticbtn(&cp, "Load an existing private key file", + IDC_LOADSTATIC, "&Load", IDC_LOAD); + staticbtn(&cp, "Save the generated key to a new file", + IDC_SAVESTATIC, "&Save", IDC_SAVE); + endbox(&cp); + } + /* + * Initially, hide the progress bar and the key display, + * and show the no-key display. Also disable the Save + * button, because with no key we obviously can't save + * anything. + */ + hidemany(hwnd, nokey_ids, FALSE); + hidemany(hwnd, generating_ids, TRUE); + hidemany(hwnd, gotkey_ids, TRUE); + EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); + + return 1; + case WM_MOUSEMOVE: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + if (state->collecting_entropy) { + state->entropy[state->entropy_got++] = lParam; + state->entropy[state->entropy_got++] = GetMessageTime(); + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, + state->entropy_got, 0); + if (state->entropy_got >= state->entropy_required) { + struct rsa_key_thread_params *params; + DWORD threadid; + + /* + * Seed the entropy pool + */ + random_add_heavynoise(state->entropy, state->entropy_size); + memset(state->entropy, 0, state->entropy_size); + free(state->entropy); + + SetDlgItemText(hwnd, IDC_GENERATING, generating_msg); + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, + MAKELPARAM(0, PROGRESSRANGE)); + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0); + + params = malloc(sizeof(*params)); + params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS); + params->dialog = hwnd; + params->key = &state->key; + params->aux = &state->aux; + + if (!CreateThread(NULL, 0, generate_rsa_key_thread, + params, 0, &threadid)) { + MessageBox(hwnd, "Out of thread resources", + "Key generation error", + MB_OK | MB_ICONERROR); + free(params); + } else { + state->generation_thread_exists = TRUE; + state->collecting_entropy = FALSE; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_COMMENTEDIT: + if (HIWORD(wParam) == EN_CHANGE) { + state = (struct MainDlgState *) + GetWindowLong(hwnd, GWL_USERDATA); + if (state->key_exists) { + HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT); + int len = GetWindowTextLength(editctl); + if (state->key.comment) + free(state->key.comment); + state->key.comment = malloc(len+1); + GetWindowText(editctl, state->key.comment, len+1); + } + } + break; + case IDC_ABOUT: + EnableWindow(hwnd, 0); + DialogBox (hinst, MAKEINTRESOURCE(213), NULL, AboutProc); + EnableWindow(hwnd, 1); + SetActiveWindow(hwnd); + return 0; + case IDC_GENERATE: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + if (!state->generation_thread_exists) { + hidemany(hwnd, nokey_ids, TRUE); + hidemany(hwnd, generating_ids, FALSE); + hidemany(hwnd, gotkey_ids, TRUE); + EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0); + EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0); + EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); + state->key_exists = FALSE; + SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); + state->collecting_entropy = TRUE; + + /* + * My brief statistical tests on mouse movements + * suggest that there are about 5 bits of + * randomness in the x position, 5 in the y + * position, and 1.7 in the message time, making + * 11.7 bits of unpredictability per mouse + * movement. However, other people have told me + * it's far less than that, so I'm going to be + * stupidly cautious and knock that down to a nice + * round 4. + */ + state->entropy_required = (KEYSIZE / 4) * 2; + state->entropy_got = 0; + state->entropy_size = (state->entropy_required * + sizeof(*state->entropy)); + state->entropy = malloc(state->entropy_size); + + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, + MAKELPARAM(0, state->entropy_required)); + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0); + } + break; + case IDC_SAVE: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + if (state->key_exists) { + char filename[FILENAME_MAX]; + char passphrase[PASSPHRASE_MAXLEN]; + GetDlgItemText(hwnd, IDC_PASSPHRASEEDIT, + passphrase, sizeof(passphrase)-1); + if (!*passphrase) { + int ret; + ret = MessageBox(hwnd, + "Are you sure you want to save this key\n" + "without a passphrase to protect it?", + "PuTTYgen Warning", + MB_YESNO | MB_ICONWARNING); + if (ret != IDYES) + break; + } + if (prompt_keyfile(hwnd, "Save private key as:", + filename, 1)) { + /* FIXME: prompt before overwriting */ + saversakey(filename, &state->key, &state->aux, + *passphrase ? passphrase : NULL); + /* FIXME: check return value */ + } + } + break; + case IDC_LOAD: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + if (!state->generation_thread_exists) { + char filename[FILENAME_MAX]; + if (prompt_keyfile(hwnd, "Load private key:", + filename, 0)) { + char passphrase[PASSPHRASE_MAXLEN]; + int needs_pass; + int ret; + char *comment; + struct PassphraseProcStruct pps; + struct RSAKey newkey; + struct RSAAux newaux; + + needs_pass = rsakey_encrypted(filename, &comment); + pps.passphrase = passphrase; + pps.comment = comment; + do { + if (needs_pass) { + int dlgret; + dlgret = DialogBoxParam(hinst, + MAKEINTRESOURCE(210), + NULL, PassphraseProc, + (LPARAM)&pps); + if (!dlgret) { + ret = -2; + break; + } + } else + *passphrase = '\0'; + ret = loadrsakey(filename, &newkey, &newaux, + passphrase); + } while (ret == -1); + if (comment) free(comment); + if (ret == 0) { + MessageBox(NULL, "Couldn't load private key.", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + } else if (ret == 1) { + state->key = newkey; + state->aux = newaux; + + EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); + EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); + EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); + /* + * Now update the key controls with all the + * key data. + */ + { + char buf[128]; + SetDlgItemText(hwnd, IDC_PASSPHRASEEDIT, + passphrase); + SetDlgItemText(hwnd, IDC_COMMENTEDIT, + state->key.comment); + /* + * Set the key fingerprint. + */ + { + char *savecomment = state->key.comment; + state->key.comment = NULL; + rsa_fingerprint(buf, sizeof(buf), &state->key); + state->key.comment = savecomment; + } + SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); + /* + * Construct a decimal representation + * of the key, for pasting into + * .ssh/authorized_keys on a Unix box. + */ + setupbigedit(hwnd, IDC_KEYDISPLAY, &state->key); + } + /* + * Finally, hide the progress bar and show + * the key data. + */ + hidemany(hwnd, nokey_ids, TRUE); + hidemany(hwnd, generating_ids, TRUE); + hidemany(hwnd, gotkey_ids, FALSE); + state->key_exists = TRUE; + } + } + } + break; + } + return 0; + case WM_DONEKEY: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + state->generation_thread_exists = FALSE; + state->key_exists = TRUE; + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0); + EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); + EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); + EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); + /* + * Invent a comment for the key. We'll do this by including + * the date in it. This will be so horrifyingly ugly that + * the user will immediately want to change it, which is + * what we want :-) + */ + state->key.comment = malloc(30); + { + time_t t; + struct tm *tm; + time(&t); + tm = localtime(&t); + strftime(state->key.comment, 30, "rsa-key-%Y%m%d", tm); + } + + /* + * Now update the key controls with all the key data. + */ + { + char buf[128]; + /* + * Blank passphrase, initially. This isn't dangerous, + * because we will warn (Are You Sure?) before allowing + * the user to save an unprotected private key. + */ + SetDlgItemText(hwnd, IDC_PASSPHRASEEDIT, ""); + /* + * Set the comment. + */ + SetDlgItemText(hwnd, IDC_COMMENTEDIT, state->key.comment); + /* + * Set the key fingerprint. + */ + { + char *savecomment = state->key.comment; + state->key.comment = NULL; + rsa_fingerprint(buf, sizeof(buf), &state->key); + state->key.comment = savecomment; + } + SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); + /* + * Construct a decimal representation of the key, for + * pasting into .ssh/authorized_keys on a Unix box. + */ + setupbigedit(hwnd, IDC_KEYDISPLAY, &state->key); + } + /* + * Finally, hide the progress bar and show the key data. + */ + hidemany(hwnd, nokey_ids, TRUE); + hidemany(hwnd, generating_ids, TRUE); + hidemany(hwnd, gotkey_ids, FALSE); + break; + case WM_CLOSE: + state = (struct MainDlgState *)GetWindowLong(hwnd, GWL_USERDATA); + free(state); + EndDialog(hwnd, 1); + return 0; + } + return 0; +} + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { + hinst = inst; + random_init(); + return DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; +} diff --git a/puttygen.rc b/puttygen.rc new file mode 100644 index 00000000..36af726b --- /dev/null +++ b/puttygen.rc @@ -0,0 +1,73 @@ +/* Some compilers, like Borland, don't have winresrc.h */ +#ifndef NO_WINRESRC_H +#include +#endif + +200 ICON "puttygen.ico" + +201 DIALOG DISCARDABLE 0, 0, 300, 300 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen Key Generator" +FONT 8, "MS Sans Serif" +BEGIN +END + +210 DIALOG DISCARDABLE 0, 0, 140, 60 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen: 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 + +/* Accelerators used: cl */ +213 DIALOG DISCARDABLE 140, 40, 136, 70 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PuTTYgen" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 82, 52, 48, 14 + PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14 + CTEXT "PuTTYgen", 102, 10, 6, 120, 8 + CTEXT "", 100, 10, 16, 120, 16 + CTEXT "\251 1997-2000 Simon Tatham. All rights reserved.", + 103, 10, 34, 120, 16 +END + +/* No accelerators used */ +214 DIALOG DISCARDABLE 50, 50, 226, 223 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen Licence" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 98, 203, 44, 14 + + LTEXT "Copyright \251 1997-2000 Simon Tatham", 1000, 10, 10, 206, 8 + + LTEXT "Permission is hereby granted, free of charge, to any person", 1002, 10, 26, 206, 8 + LTEXT "obtaining a copy of this software and associated documentation", 1003, 10, 34, 206, 8 + LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1004, 10, 42, 206, 8 + LTEXT "including without limitation the rights to use, copy, modify, merge,", 1005, 10, 50, 206, 8 + LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1006, 10, 58, 206, 8 + LTEXT "and to permit persons to whom the Software is furnished to do so,", 1007, 10, 66, 206, 8 + LTEXT "subject to the following conditions:", 1008, 10, 74, 206, 8 + + LTEXT "The above copyright notice and this permission notice shall be", 1010, 10, 90, 206, 8 + LTEXT "included in all copies or substantial portions of the Software.", 1011, 10, 98, 206, 8 + + LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1013, 10, 114, 206, 8 + LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1014, 10, 122, 206, 8 + LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1015, 10, 130, 206, 8 + LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1016, 10, 138, 206, 8 + LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL", 1017, 10, 146, 206, 8 + LTEXT "SIMON TATHAM BE LIABLE FOR ANY CLAIM, DAMAGES OR", 1018, 10, 154, 206, 8 + LTEXT "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1019, 10, 162, 206, 8 + LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1020, 10, 170, 206, 8 + LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1021, 10, 178, 206, 8 + LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1022, 10, 186, 206, 8 + +END diff --git a/ssh.c b/ssh.c index 00958356..85324b28 100644 --- a/ssh.c +++ b/ssh.c @@ -1531,7 +1531,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static unsigned char buffer[32]; tried_publickey = 1; - i = loadrsakey(cfg.keyfile, &pubkey, password); + i = loadrsakey(cfg.keyfile, &pubkey, NULL, password); if (i == 0) { c_write("Couldn't load public key from ", 30); c_write(cfg.keyfile, strlen(cfg.keyfile)); diff --git a/ssh.h b/ssh.h index a7189039..5fe33c3b 100644 --- a/ssh.h +++ b/ssh.h @@ -145,6 +145,7 @@ void SHATransform(word32 *digest, word32 *data); int random_byte(void); void random_add_noise(void *noise, int length); +void random_add_heavynoise(void *noise, int length); void logevent (char *); @@ -170,15 +171,22 @@ Bignum bigmul(Bignum a, Bignum b); Bignum modinv(Bignum number, Bignum modulus); Bignum bignum_rshift(Bignum number, int shift); int bignum_cmp(Bignum a, Bignum b); +char *bignum_decimal(Bignum x); Bignum dh_create_e(void); Bignum dh_find_K(Bignum f); -int loadrsakey(char *filename, struct RSAKey *key, char *passphrase); +int loadrsakey(char *filename, struct RSAKey *key, struct RSAAux *aux, + char *passphrase); int rsakey_encrypted(char *filename, char **comment); +int saversakey(char *filename, struct RSAKey *key, struct RSAAux *aux, + char *passphrase); + void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); +void des3_encrypt_pubkey(unsigned char *key, + unsigned char *blk, int len); /* * For progress updates in the key generation utility. diff --git a/sshbn.c b/sshbn.c index 51e59015..693b4ac7 100644 --- a/sshbn.c +++ b/sshbn.c @@ -65,17 +65,17 @@ static void internal_mul(unsigned short *a, unsigned short *b, } } -static int internal_add_shifted(unsigned short *number, - unsigned short n, int shift) { +static void internal_add_shifted(unsigned short *number, + unsigned n, int shift) { int word = 1 + (shift / 16); int bshift = shift % 16; - unsigned long carry, addend; + unsigned long addend; addend = n << bshift; while (addend) { addend += number[word]; - number[word] = addend & 0xFFFF; + number[word] = (unsigned short) addend & 0xFFFF; addend >>= 16; word++; } @@ -577,7 +577,7 @@ Bignum bigmuladd(Bignum a, Bignum b, Bignum addend) { for (i = 1; i <= rlen; i++) { carry += (i <= ret[0] ? ret[i] : 0); carry += (i <= addend[0] ? addend[i] : 0); - ret[i] = carry & 0xFFFF; + ret[i] = (unsigned short) carry & 0xFFFF; carry >>= 16; if (ret[i] != 0 && i > maxspot) maxspot = i; @@ -620,7 +620,7 @@ Bignum bignum_add_long(Bignum number, unsigned long addend) { carry += addend & 0xFFFF; carry += (i <= number[0] ? number[i] : 0); addend >>= 16; - ret[i] = carry & 0xFFFF; + ret[i] = (unsigned short) carry & 0xFFFF; carry >>= 16; if (ret[i] != 0) maxspot = i; @@ -633,7 +633,6 @@ Bignum bignum_add_long(Bignum number, unsigned long addend) { * Compute the residue of a bignum, modulo a (max 16-bit) short. */ unsigned short bignum_mod_short(Bignum number, unsigned short modulus) { - Bignum ret; unsigned long mod, r; int i; @@ -641,7 +640,7 @@ unsigned short bignum_mod_short(Bignum number, unsigned short modulus) { mod = modulus; for (i = number[0]; i > 0; i--) r = (r * 65536 + number[i]) % mod; - return r; + return (unsigned short) r; } static void diagbn(char *prefix, Bignum md) { @@ -736,3 +735,74 @@ Bignum modinv(Bignum number, Bignum modulus) { /* and return. */ return x; } + +/* + * Render a bignum into decimal. Return a malloced string holding + * the decimal representation. + */ +char *bignum_decimal(Bignum x) { + int ndigits, ndigit; + int i, iszero; + unsigned long carry; + char *ret; + unsigned short *workspace; + + /* + * First, estimate the number of digits. Since log(10)/log(2) + * is just greater than 93/28 (the joys of continued fraction + * approximations...) we know that for every 93 bits, we need + * at most 28 digits. This will tell us how much to malloc. + * + * Formally: if x has i bits, that means x is strictly less + * than 2^i. Since 2 is less than 10^(28/93), this is less than + * 10^(28i/93). We need an integer power of ten, so we must + * round up (rounding down might make it less than x again). + * Therefore if we multiply the bit count by 28/93, rounding + * up, we will have enough digits. + */ + i = ssh1_bignum_bitcount(x); + ndigits = (28*i + 92)/93; /* multiply by 28/93 and round up */ + ndigits++; /* allow for trailing \0 */ + ret = malloc(ndigits); + + /* + * Now allocate some workspace to hold the binary form as we + * repeatedly divide it by ten. Initialise this to the + * big-endian form of the number. + */ + workspace = malloc(sizeof(unsigned short) * x[0]); + for (i = 0; i < x[0]; i++) + workspace[i] = x[x[0] - i]; + + /* + * Next, write the decimal number starting with the last digit. + * We use ordinary short division, dividing 10 into the + * workspace. + */ + ndigit = ndigits-1; + ret[ndigit] = '\0'; + do { + iszero = 1; + carry = 0; + for (i = 0; i < x[0]; i++) { + carry = (carry << 16) + workspace[i]; + workspace[i] = (unsigned short) (carry / 10); + if (workspace[i]) + iszero = 0; + carry %= 10; + } + ret[--ndigit] = (char)(carry + '0'); + } while (!iszero); + + /* + * There's a chance we've fallen short of the start of the + * string. Correct if so. + */ + if (ndigit > 0) + memmove(ret, ret+ndigit, ndigits-ndigit); + + /* + * Done. + */ + return ret; +} diff --git a/sshdes.c b/sshdes.c index 67d91c7f..1be340c4 100644 --- a/sshdes.c +++ b/sshdes.c @@ -778,6 +778,18 @@ void des3_decrypt_pubkey(unsigned char *key, des_3cbc_decrypt(blk, blk, len, ourkeys); } +void des3_encrypt_pubkey(unsigned char *key, + unsigned char *blk, int len) { + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key+4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key+8), + GET_32BIT_MSB_FIRST(key+12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key+4), &ourkeys[2]); + des_3cbc_encrypt(blk, blk, len, ourkeys); +} + struct ssh_cipher ssh_3des_ssh2 = { NULL, des3_csiv, des3_cskey, diff --git a/sshpubk.c b/sshpubk.c index d58e30a5..fd40db77 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -10,6 +10,12 @@ #include "ssh.h" +#define PUT_32BIT(cp, value) do { \ + (cp)[3] = (value); \ + (cp)[2] = (value) >> 8; \ + (cp)[1] = (value) >> 16; \ + (cp)[0] = (value) >> 24; } while (0) + #define GET_32BIT(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 24) | \ ((unsigned long)(unsigned char)(cp)[1] << 16) | \ @@ -24,7 +30,7 @@ (x)=='+' ? 62 : \ (x)=='/' ? 63 : 0 ) -static int loadrsakey_main(FILE *fp, struct RSAKey *key, +static int loadrsakey_main(FILE *fp, struct RSAKey *key, struct RSAAux *aux, char **commentptr, char *passphrase) { unsigned char buf[16384]; unsigned char keybuf[16]; @@ -106,10 +112,19 @@ static int loadrsakey_main(FILE *fp, struct RSAKey *key, /* * After that, we have one further bignum which is our - * decryption modulus, and then we're done. + * decryption exponent, and then the three auxiliary values + * (iqmp, q, p). */ i += makeprivate(buf+i, key); if (len-i < 0) goto end; + if (aux) { + i += ssh1_read_bignum(buf+i, &aux->iqmp); + if (len-i < 0) goto end; + i += ssh1_read_bignum(buf+i, &aux->q); + if (len-i < 0) goto end; + i += ssh1_read_bignum(buf+i, &aux->p); + if (len-i < 0) goto end; + } ret = 1; end: @@ -117,7 +132,8 @@ static int loadrsakey_main(FILE *fp, struct RSAKey *key, return ret; } -int loadrsakey(char *filename, struct RSAKey *key, char *passphrase) { +int loadrsakey(char *filename, struct RSAKey *key, struct RSAAux *aux, + char *passphrase) { FILE *fp; unsigned char buf[64]; @@ -131,7 +147,7 @@ int loadrsakey(char *filename, struct RSAKey *key, char *passphrase) { */ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { - return loadrsakey_main(fp, key, NULL, passphrase); + return loadrsakey_main(fp, key, aux, NULL, passphrase); } /* @@ -159,7 +175,103 @@ int rsakey_encrypted(char *filename, char **comment) { */ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { - return loadrsakey_main(fp, NULL, comment, NULL); + return loadrsakey_main(fp, NULL, NULL, comment, NULL); } return 0; /* wasn't the right kind of file */ } + +/* + * Save an RSA key file. Return nonzero on success. + */ +int saversakey(char *filename, struct RSAKey *key, struct RSAAux *aux, + char *passphrase) { + unsigned char buf[16384]; + unsigned char keybuf[16]; + struct MD5Context md5c; + char *p, *estart; + FILE *fp; + + /* + * Write the initial signature. + */ + p = buf; + memcpy(p, rsa_signature, sizeof(rsa_signature)); + p += sizeof(rsa_signature); + + /* + * One byte giving encryption type, and one reserved (zero) + * uint32. + */ + *p++ = (passphrase ? SSH_CIPHER_3DES : 0); + PUT_32BIT(p, 0); p += 4; + + /* + * An ordinary SSH 1 public key consists of: a uint32 + * containing the bit count, then two bignums containing the + * modulus and exponent respectively. + */ + PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus)); p += 4; + p += ssh1_write_bignum(p, key->modulus); + p += ssh1_write_bignum(p, key->exponent); + + /* + * A string containing the comment field. + */ + if (key->comment) { + PUT_32BIT(p, strlen(key->comment)); p += 4; + memcpy(p, key->comment, strlen(key->comment)); + p += strlen(key->comment); + } else { + PUT_32BIT(p, 0); p += 4; + } + + /* + * The encrypted portion starts here. + */ + estart = p; + + /* + * Two bytes, then the same two bytes repeated. + */ + *p++ = random_byte(); + *p++ = random_byte(); + p[0] = p[-2]; p[1] = p[-1]; p += 2; + + /* + * Four more bignums: the decryption exponent, then iqmp, then + * q, then p. + */ + p += ssh1_write_bignum(p, key->private_exponent); + p += ssh1_write_bignum(p, aux->iqmp); + p += ssh1_write_bignum(p, aux->q); + p += ssh1_write_bignum(p, aux->p); + + /* + * Now write zeros until the encrypted portion is a multiple of + * 8 bytes. + */ + while ((p-estart) % 8) + *p++ = '\0'; + + /* + * Now encrypt the encrypted portion. + */ + if (passphrase) { + MD5Init(&md5c); + MD5Update(&md5c, passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + des3_encrypt_pubkey(keybuf, estart, p-estart); + memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ + } + + /* + * Done. Write the result to the file. + */ + fp = fopen(filename, "wb"); + if (fp) { + int ret = (fwrite(buf, 1, p-buf, fp) == (size_t)(p-buf)); + ret = ret && (fclose(fp) == 0); + return ret; + } else + return 0; +} diff --git a/sshrand.c b/sshrand.c index e739113a..84c3e91e 100644 --- a/sshrand.c +++ b/sshrand.c @@ -40,22 +40,6 @@ struct RandPool { static struct RandPool pool; -void random_add_noise(void *noise, int length) { - unsigned char *p = noise; - - while (length >= (HASHINPUT - pool.incomingpos)) { - memcpy(pool.incomingb + pool.incomingpos, p, - HASHINPUT - pool.incomingpos); - p += HASHINPUT - pool.incomingpos; - length -= HASHINPUT - pool.incomingpos; - SHATransform((word32 *)pool.incoming, (word32 *)pool.incomingb); - pool.incomingpos = 0; - } - - memcpy(pool.incomingb + pool.incomingpos, p, length); - pool.incomingpos += length; -} - void random_stir(void) { word32 block[HASHINPUT/sizeof(word32)]; word32 digest[HASHSIZE/sizeof(word32)]; @@ -126,33 +110,73 @@ void random_stir(void) { pool.poolpos = sizeof(pool.incoming); } -static void random_add_heavynoise(void *noise, int length) { +void random_add_noise(void *noise, int length) { unsigned char *p = noise; + int i; - while (length >= (POOLSIZE - pool.poolpos)) { - memcpy(pool.pool + pool.poolpos, p, POOLSIZE - pool.poolpos); - p += POOLSIZE - pool.poolpos; - length -= POOLSIZE - pool.poolpos; + /* + * This function processes HASHINPUT bytes into only HASHSIZE + * bytes, so _if_ we were getting incredibly high entropy + * sources then we would be throwing away valuable stuff. + */ + while (length >= (HASHINPUT - pool.incomingpos)) { + memcpy(pool.incomingb + pool.incomingpos, p, + HASHINPUT - pool.incomingpos); + p += HASHINPUT - pool.incomingpos; + length -= HASHINPUT - pool.incomingpos; + SHATransform((word32 *)pool.incoming, (word32 *)pool.incomingb); + for (i = 0; i < HASHSIZE; i++) { + pool.pool[pool.poolpos++] ^= pool.incomingb[i]; + if (pool.poolpos >= POOLSIZE) + pool.poolpos = 0; + } + if (pool.poolpos < HASHSIZE) + random_stir(); + + pool.incomingpos = 0; + } + + memcpy(pool.incomingb + pool.incomingpos, p, length); + pool.incomingpos += length; +} + +void random_add_heavynoise(void *noise, int length) { + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE) { + for (i = 0; i < POOLSIZE; i++) + pool.pool[i] ^= *p++; + random_stir(); + length -= POOLSIZE; + } + + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + random_stir(); +} + +static void random_add_heavynoise_bitbybit(void *noise, int length) { + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE - pool.poolpos) { + for (i = 0; i < POOLSIZE - pool.poolpos; i++) + pool.pool[pool.poolpos + i] ^= *p++; random_stir(); - pool.poolpos = 0; + length -= POOLSIZE - pool.poolpos; + pool.poolpos = 0; } - memcpy(pool.pool + pool.poolpos, p, length); - pool.poolpos += length; + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + pool.poolpos = i; } void random_init(void) { memset(&pool, 0, sizeof(pool)); /* just to start with */ - /* - * For noise_get_heavy, we temporarily use `poolpos' as the - * pointer for addition of noise, rather than extraction of - * random numbers. - */ - pool.poolpos = 0; - noise_get_heavy(random_add_heavynoise); - - random_stir(); + noise_get_heavy(random_add_heavynoise_bitbybit); } int random_byte(void) { diff --git a/winctrls.c b/winctrls.c index 3a595034..8ed1528c 100644 --- a/winctrls.c +++ b/winctrls.c @@ -4,6 +4,7 @@ */ #include +#include #include "winstuff.h" @@ -18,6 +19,7 @@ #define EDITHEIGHT 12 #define COMBOHEIGHT 12 #define PUSHBTNHEIGHT 14 +#define PROGBARHEIGHT 14 void ctlposinit(struct ctlpos *cp, HWND hwnd, int leftborder, int rightborder, int topborder) { @@ -247,6 +249,18 @@ void checkbox(struct ctlpos *cp, char *text, int id) { } /* + * A single standalone static text control. + */ +void statictext(struct ctlpos *cp, char *text, int id) { + RECT r; + + r.left = GAPBETWEEN; r.top = cp->ypos; + r.right = cp->width; r.bottom = STATICHEIGHT; + cp->ypos += r.bottom + GAPBETWEEN; + doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id); +} + +/* * A button on the right hand side, with a static to its left. */ void staticbtn(struct ctlpos *cp, char *stext, int sid, @@ -277,8 +291,9 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid, /* * An edit control on the right hand side, with a static to its left. */ -void staticedit(struct ctlpos *cp, char *stext, - int sid, int eid, int percentedit) { +static void staticedit_internal(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit, + int style) { const int height = (EDITHEIGHT > STATICHEIGHT ? EDITHEIGHT : STATICHEIGHT); RECT r; @@ -295,13 +310,44 @@ void staticedit(struct ctlpos *cp, char *stext, r.left = rpos; r.top = cp->ypos + (height-EDITHEIGHT)/2; r.right = rwid; r.bottom = EDITHEIGHT; doctl(cp, r, "EDIT", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, + WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style, WS_EX_CLIENTEDGE, "", eid); cp->ypos += height + GAPBETWEEN; } +void staticedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit) { + staticedit_internal(cp, stext, sid, eid, percentedit, 0); +} + +void staticpassedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit) { + staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD); +} + +/* + * A big multiline edit control with a static labelling it. + */ +void bigeditctrl(struct ctlpos *cp, char *stext, + int sid, int eid, int lines) { + RECT r; + + r.left = GAPBETWEEN; r.top = cp->ypos; + r.right = cp->width; r.bottom = STATICHEIGHT; + cp->ypos += r.bottom + GAPWITHIN; + doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); + + r.left = GAPBETWEEN; r.top = cp->ypos; + r.right = cp->width; r.bottom = EDITHEIGHT + (lines-1) * STATICHEIGHT; + cp->ypos += r.bottom + GAPBETWEEN; + doctl(cp, r, "EDIT", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE, + WS_EX_CLIENTEDGE, + "", eid); +} + /* * A tab-control substitute when a real tab control is unavailable. */ @@ -633,3 +679,22 @@ void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, cp->ypos += LISTHEIGHT + GAPBETWEEN; } +/* + * A progress bar (from Common Controls). We like our progress bars + * to be smooth and unbroken, without those ugly divisions; some + * older compilers may not support that, but that's life. + */ +void progressbar(struct ctlpos *cp, int id) { + RECT r; + + r.left = GAPBETWEEN; r.top = cp->ypos; + r.right = cp->width; r.bottom = PROGBARHEIGHT; + cp->ypos += r.bottom + GAPBETWEEN; + + doctl(cp, r, PROGRESS_CLASS, + WS_CHILD | WS_VISIBLE +#ifdef PBS_SMOOTH + | PBS_SMOOTH +#endif + , WS_EX_CLIENTEDGE, "", id); +} diff --git a/winstuff.h b/winstuff.h index 02f4c909..ea2f48a5 100644 --- a/winstuff.h +++ b/winstuff.h @@ -42,10 +42,15 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); void radiobig(struct ctlpos *cp, char *text, int id, ...); void checkbox(struct ctlpos *cp, char *text, int id); +void statictext(struct ctlpos *cp, char *text, int id); void staticbtn(struct ctlpos *cp, char *stext, int sid, char *btext, int bid); void staticedit(struct ctlpos *cp, char *stext, int sid, int eid, int percentedit); +void staticpassedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void bigeditctrl(struct ctlpos *cp, char *stext, + int sid, int eid, int lines); void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id); void editbutton(struct ctlpos *cp, char *stext, int sid, @@ -61,3 +66,4 @@ void charclass(struct ctlpos *cp, char *stext, int sid, int listid, char *btext, int bid, int eid, char *s2text, int s2id); void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, char *btext, int bid, ...); +void progressbar(struct ctlpos *cp, int id); -- 2.11.0