Added a framework for importing foreign key formats, and implemented
[u/mdw/putty] / puttygen.c
1 /*
2 * PuTTY key generation front end.
3 */
4
5 #include <windows.h>
6 #include <commctrl.h>
7 #include <time.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10
11 #define PUTTY_DO_GLOBALS
12
13 #include "putty.h"
14 #include "ssh.h"
15 #include "winstuff.h"
16
17 #define WM_DONEKEY (WM_XUSER + 1)
18
19 #define DEFAULT_KEYSIZE 1024
20
21 static int requested_help;
22
23 /* ----------------------------------------------------------------------
24 * Progress report code. This is really horrible :-)
25 */
26 #define PROGRESSRANGE 65535
27 #define MAXPHASE 5
28 struct progress {
29 int nphases;
30 struct {
31 int exponential;
32 unsigned startpoint, total;
33 unsigned param, current, n; /* if exponential */
34 unsigned mult; /* if linear */
35 } phases[MAXPHASE];
36 unsigned total, divisor, range;
37 HWND progbar;
38 };
39
40 static void progress_update(void *param, int action, int phase, int iprogress)
41 {
42 struct progress *p = (struct progress *) param;
43 unsigned progress = iprogress;
44 int position;
45
46 if (action < PROGFN_READY && p->nphases < phase)
47 p->nphases = phase;
48 switch (action) {
49 case PROGFN_INITIALISE:
50 p->nphases = 0;
51 break;
52 case PROGFN_LIN_PHASE:
53 p->phases[phase-1].exponential = 0;
54 p->phases[phase-1].mult = p->phases[phase].total / progress;
55 break;
56 case PROGFN_EXP_PHASE:
57 p->phases[phase-1].exponential = 1;
58 p->phases[phase-1].param = 0x10000 + progress;
59 p->phases[phase-1].current = p->phases[phase-1].total;
60 p->phases[phase-1].n = 0;
61 break;
62 case PROGFN_PHASE_EXTENT:
63 p->phases[phase-1].total = progress;
64 break;
65 case PROGFN_READY:
66 {
67 unsigned total = 0;
68 int i;
69 for (i = 0; i < p->nphases; i++) {
70 p->phases[i].startpoint = total;
71 total += p->phases[i].total;
72 }
73 p->total = total;
74 p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE);
75 p->range = p->total / p->divisor;
76 SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range));
77 }
78 break;
79 case PROGFN_PROGRESS:
80 if (p->phases[phase-1].exponential) {
81 while (p->phases[phase-1].n < progress) {
82 p->phases[phase-1].n++;
83 p->phases[phase-1].current *= p->phases[phase-1].param;
84 p->phases[phase-1].current /= 0x10000;
85 }
86 position = (p->phases[phase-1].startpoint +
87 p->phases[phase-1].total - p->phases[phase-1].current);
88 } else {
89 position = (p->phases[phase-1].startpoint +
90 progress * p->phases[phase-1].mult);
91 }
92 SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0);
93 break;
94 }
95 }
96
97 extern char ver[];
98
99 #define PASSPHRASE_MAXLEN 512
100
101 struct PassphraseProcStruct {
102 char *passphrase;
103 char *comment;
104 };
105
106 /*
107 * Dialog-box function for the passphrase box.
108 */
109 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
110 WPARAM wParam, LPARAM lParam)
111 {
112 static char *passphrase = NULL;
113 struct PassphraseProcStruct *p;
114
115 switch (msg) {
116 case WM_INITDIALOG:
117 SetForegroundWindow(hwnd);
118 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
119 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
120
121 /*
122 * Centre the window.
123 */
124 { /* centre the window */
125 RECT rs, rd;
126 HWND hw;
127
128 hw = GetDesktopWindow();
129 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
130 MoveWindow(hwnd,
131 (rs.right + rs.left + rd.left - rd.right) / 2,
132 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
133 rd.right - rd.left, rd.bottom - rd.top, TRUE);
134 }
135
136 p = (struct PassphraseProcStruct *) lParam;
137 passphrase = p->passphrase;
138 if (p->comment)
139 SetDlgItemText(hwnd, 101, p->comment);
140 *passphrase = 0;
141 SetDlgItemText(hwnd, 102, passphrase);
142 return 0;
143 case WM_COMMAND:
144 switch (LOWORD(wParam)) {
145 case IDOK:
146 if (*passphrase)
147 EndDialog(hwnd, 1);
148 else
149 MessageBeep(0);
150 return 0;
151 case IDCANCEL:
152 EndDialog(hwnd, 0);
153 return 0;
154 case 102: /* edit box */
155 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
156 GetDlgItemText(hwnd, 102, passphrase,
157 PASSPHRASE_MAXLEN - 1);
158 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
159 }
160 return 0;
161 }
162 return 0;
163 case WM_CLOSE:
164 EndDialog(hwnd, 0);
165 return 0;
166 }
167 return 0;
168 }
169
170 /*
171 * Prompt for a key file. Assumes the filename buffer is of size
172 * FILENAME_MAX.
173 */
174 static int prompt_keyfile(HWND hwnd, char *dlgtitle,
175 char *filename, int save)
176 {
177 OPENFILENAME of;
178 memset(&of, 0, sizeof(of));
179 #ifdef OPENFILENAME_SIZE_VERSION_400
180 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
181 #else
182 of.lStructSize = sizeof(of);
183 #endif
184 of.hwndOwner = hwnd;
185 of.lpstrFilter = "All Files\0*\0\0\0";
186 of.lpstrCustomFilter = NULL;
187 of.nFilterIndex = 1;
188 of.lpstrFile = filename;
189 *filename = '\0';
190 of.nMaxFile = FILENAME_MAX;
191 of.lpstrFileTitle = NULL;
192 of.lpstrInitialDir = NULL;
193 of.lpstrTitle = dlgtitle;
194 of.Flags = 0;
195 if (save)
196 return GetSaveFileName(&of);
197 else
198 return GetOpenFileName(&of);
199 }
200
201 /*
202 * This function is needed to link with the DES code. We need not
203 * have it do anything at all.
204 */
205 void logevent(char *msg)
206 {
207 }
208
209 /*
210 * Dialog-box function for the Licence box.
211 */
212 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
213 WPARAM wParam, LPARAM lParam)
214 {
215 switch (msg) {
216 case WM_INITDIALOG:
217 /*
218 * Centre the window.
219 */
220 { /* centre the window */
221 RECT rs, rd;
222 HWND hw;
223
224 hw = GetDesktopWindow();
225 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
226 MoveWindow(hwnd,
227 (rs.right + rs.left + rd.left - rd.right) / 2,
228 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
229 rd.right - rd.left, rd.bottom - rd.top, TRUE);
230 }
231
232 return 1;
233 case WM_COMMAND:
234 switch (LOWORD(wParam)) {
235 case IDOK:
236 EndDialog(hwnd, 1);
237 return 0;
238 }
239 return 0;
240 case WM_CLOSE:
241 EndDialog(hwnd, 1);
242 return 0;
243 }
244 return 0;
245 }
246
247 /*
248 * Dialog-box function for the About box.
249 */
250 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
251 WPARAM wParam, LPARAM lParam)
252 {
253 switch (msg) {
254 case WM_INITDIALOG:
255 /*
256 * Centre the window.
257 */
258 { /* centre the window */
259 RECT rs, rd;
260 HWND hw;
261
262 hw = GetDesktopWindow();
263 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
264 MoveWindow(hwnd,
265 (rs.right + rs.left + rd.left - rd.right) / 2,
266 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
267 rd.right - rd.left, rd.bottom - rd.top, TRUE);
268 }
269
270 SetDlgItemText(hwnd, 100, ver);
271 return 1;
272 case WM_COMMAND:
273 switch (LOWORD(wParam)) {
274 case IDOK:
275 EndDialog(hwnd, 1);
276 return 0;
277 case 101:
278 EnableWindow(hwnd, 0);
279 DialogBox(hinst, MAKEINTRESOURCE(214), NULL, LicenceProc);
280 EnableWindow(hwnd, 1);
281 SetActiveWindow(hwnd);
282 return 0;
283 }
284 return 0;
285 case WM_CLOSE:
286 EndDialog(hwnd, 1);
287 return 0;
288 }
289 return 0;
290 }
291
292 /*
293 * Thread to generate a key.
294 */
295 struct rsa_key_thread_params {
296 HWND progressbar; /* notify this with progress */
297 HWND dialog; /* notify this on completion */
298 int keysize; /* bits in key */
299 int is_dsa;
300 struct RSAKey *key;
301 struct dss_key *dsskey;
302 };
303 static DWORD WINAPI generate_rsa_key_thread(void *param)
304 {
305 struct rsa_key_thread_params *params =
306 (struct rsa_key_thread_params *) param;
307 struct progress prog;
308 prog.progbar = params->progressbar;
309
310 progress_update(&prog, PROGFN_INITIALISE, 0, 0);
311
312 if (params->is_dsa)
313 dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
314 else
315 rsa_generate(params->key, params->keysize, progress_update, &prog);
316
317 PostMessage(params->dialog, WM_DONEKEY, 0, 0);
318
319 sfree(params);
320 return 0;
321 }
322
323 struct MainDlgState {
324 int collecting_entropy;
325 int generation_thread_exists;
326 int key_exists;
327 int entropy_got, entropy_required, entropy_size;
328 int keysize;
329 int ssh2, is_dsa;
330 char **commentptr; /* points to key.comment or ssh2key.comment */
331 struct ssh2_userkey ssh2key;
332 unsigned *entropy;
333 struct RSAKey key;
334 struct dss_key dsskey;
335 };
336
337 static void hidemany(HWND hwnd, const int *ids, int hideit)
338 {
339 while (*ids) {
340 ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW));
341 }
342 }
343
344 static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
345 {
346 char *buffer;
347 char *dec1, *dec2;
348
349 dec1 = bignum_decimal(key->exponent);
350 dec2 = bignum_decimal(key->modulus);
351 buffer = smalloc(strlen(dec1) + strlen(dec2) +
352 strlen(key->comment) + 30);
353 sprintf(buffer, "%d %s %s %s",
354 bignum_bitcount(key->modulus), dec1, dec2, key->comment);
355 SetDlgItemText(hwnd, id, buffer);
356 SetDlgItemText(hwnd, idstatic,
357 "&Public key for pasting into authorized_keys file:");
358 sfree(dec1);
359 sfree(dec2);
360 sfree(buffer);
361 }
362
363 static void setupbigedit2(HWND hwnd, int id, int idstatic,
364 struct ssh2_userkey *key)
365 {
366 unsigned char *pub_blob;
367 char *buffer, *p;
368 int pub_len;
369 int i;
370
371 pub_blob = key->alg->public_blob(key->data, &pub_len);
372 buffer = smalloc(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) +
373 strlen(key->comment) + 3);
374 strcpy(buffer, key->alg->name);
375 p = buffer + strlen(buffer);
376 *p++ = ' ';
377 i = 0;
378 while (i < pub_len) {
379 int n = (pub_len - i < 3 ? pub_len - i : 3);
380 base64_encode_atom(pub_blob + i, n, p);
381 i += n;
382 p += 4;
383 }
384 *p++ = ' ';
385 strcpy(p, key->comment);
386 SetDlgItemText(hwnd, id, buffer);
387 SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
388 "OpenSSH authorized_keys2 file:");
389 sfree(pub_blob);
390 sfree(buffer);
391 }
392
393 static int save_ssh1_pubkey(char *filename, struct RSAKey *key)
394 {
395 char *dec1, *dec2;
396 FILE *fp;
397
398 dec1 = bignum_decimal(key->exponent);
399 dec2 = bignum_decimal(key->modulus);
400 fp = fopen(filename, "wb");
401 if (!fp)
402 return 0;
403 fprintf(fp, "%d %s %s %s\n",
404 bignum_bitcount(key->modulus), dec1, dec2, key->comment);
405 fclose(fp);
406 sfree(dec1);
407 sfree(dec2);
408 return 1;
409 }
410
411 /*
412 * Warn about the obsolescent key file format.
413 */
414 void old_keyfile_warning(void)
415 {
416 static const char mbtitle[] = "PuTTY Key File Warning";
417 static const char message[] =
418 "You are loading an SSH 2 private key which has an\n"
419 "old version of the file format. This means your key\n"
420 "file is not fully tamperproof. Future versions of\n"
421 "PuTTY may stop supporting this private key format,\n"
422 "so we recommend you convert your key to the new\n"
423 "format.\n"
424 "\n"
425 "Once the key is loaded into PuTTYgen, you can perform\n"
426 "this conversion simply by saving it again.";
427
428 MessageBox(NULL, message, mbtitle, MB_OK);
429 }
430
431 static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
432 {
433 unsigned char *pub_blob;
434 char *p;
435 int pub_len;
436 int i, column;
437 FILE *fp;
438
439 pub_blob = key->alg->public_blob(key->data, &pub_len);
440
441 fp = fopen(filename, "wb");
442 if (!fp)
443 return 0;
444
445 fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
446
447 fprintf(fp, "Comment: \"");
448 for (p = key->comment; *p; p++) {
449 if (*p == '\\' || *p == '\"')
450 fputc('\\', fp);
451 fputc(*p, fp);
452 }
453 fprintf(fp, "\"\n");
454
455 i = 0;
456 column = 0;
457 while (i < pub_len) {
458 char buf[5];
459 int n = (pub_len - i < 3 ? pub_len - i : 3);
460 base64_encode_atom(pub_blob + i, n, buf);
461 i += n;
462 buf[4] = '\0';
463 fputs(buf, fp);
464 if (++column >= 16) {
465 fputc('\n', fp);
466 column = 0;
467 }
468 }
469 if (column > 0)
470 fputc('\n', fp);
471
472 fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
473 fclose(fp);
474 sfree(pub_blob);
475 return 1;
476 }
477
478 /*
479 * Dialog-box function for the main PuTTYgen dialog box.
480 */
481 static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
482 WPARAM wParam, LPARAM lParam)
483 {
484 enum {
485 controlidstart = 100,
486 IDC_TITLE,
487 IDC_BOX_KEY,
488 IDC_NOKEY,
489 IDC_GENERATING,
490 IDC_PROGRESS,
491 IDC_PKSTATIC, IDC_KEYDISPLAY,
492 IDC_FPSTATIC, IDC_FINGERPRINT,
493 IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
494 IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
495 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT,
496 IDC_BOX_ACTIONS,
497 IDC_GENSTATIC, IDC_GENERATE,
498 IDC_LOADSTATIC, IDC_LOAD,
499 IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
500 IDC_BOX_PARAMS,
501 IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
502 IDC_BITSSTATIC, IDC_BITS,
503 IDC_ABOUT,
504 IDC_GIVEHELP,
505 IDC_IMPORT, IDC_EXPORT_OPENSSH, IDC_EXPORT_SSHCOM
506 };
507 static const int nokey_ids[] = { IDC_NOKEY, 0 };
508 static const int generating_ids[] =
509 { IDC_GENERATING, IDC_PROGRESS, 0 };
510 static const int gotkey_ids[] = {
511 IDC_PKSTATIC, IDC_KEYDISPLAY,
512 IDC_FPSTATIC, IDC_FINGERPRINT,
513 IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
514 IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
515 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0
516 };
517 static const char generating_msg[] =
518 "Please wait while a key is generated...";
519 static const char entropy_msg[] =
520 "Please generate some randomness by moving the mouse over the blank area.";
521 struct MainDlgState *state;
522
523 switch (msg) {
524 case WM_INITDIALOG:
525 if (help_path)
526 SetWindowLong(hwnd, GWL_EXSTYLE,
527 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
528 else {
529 /*
530 * If we add a Help button, this is where we destroy it
531 * if the help file isn't present.
532 */
533 }
534 requested_help = FALSE;
535
536 {
537 HMENU menu, menu1;
538
539 menu = CreateMenu();
540
541 menu1 = CreateMenu();
542 AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
543 AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key");
544 AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key");
545 AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
546
547 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&File");
548
549 #if 0
550 /*
551 * Exporting not yet supported, but when we do it we
552 * should just put this lot back in.
553 */
554 menu1 = CreateMenu();
555 AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH,
556 "Export &OpenSSH key");
557 AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
558 "Export &ssh.com key");
559
560 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1,
561 "&Export");
562 #endif
563
564 menu1 = CreateMenu();
565 AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
566 if (help_path)
567 AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
568
569 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Help");
570
571 SetMenu(hwnd, menu);
572 }
573
574 /*
575 * Centre the window.
576 */
577 { /* centre the window */
578 RECT rs, rd;
579 HWND hw;
580
581 hw = GetDesktopWindow();
582 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
583 MoveWindow(hwnd,
584 (rs.right + rs.left + rd.left - rd.right) / 2,
585 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
586 rd.right - rd.left, rd.bottom - rd.top, TRUE);
587 }
588
589 state = smalloc(sizeof(*state));
590 state->generation_thread_exists = FALSE;
591 state->collecting_entropy = FALSE;
592 state->entropy = NULL;
593 state->key_exists = FALSE;
594 SetWindowLong(hwnd, GWL_USERDATA, (LONG) state);
595 {
596 struct ctlpos cp, cp2;
597
598 /* Accelerators used: acglops1rbd */
599
600 ctlposinit(&cp, hwnd, 4, 4, 4);
601 beginbox(&cp, "Key", IDC_BOX_KEY);
602 cp2 = cp;
603 statictext(&cp2, "No key.", 1, IDC_NOKEY);
604 cp2 = cp;
605 statictext(&cp2, "", 1, IDC_GENERATING);
606 progressbar(&cp2, IDC_PROGRESS);
607 bigeditctrl(&cp,
608 "&Public key for pasting into authorized_keys file:",
609 IDC_PKSTATIC, IDC_KEYDISPLAY, 5);
610 SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0);
611 staticedit(&cp, "Key fingerprint:", IDC_FPSTATIC,
612 IDC_FINGERPRINT, 75);
613 SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1,
614 0);
615 staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC,
616 IDC_COMMENTEDIT, 75);
617 staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC,
618 IDC_PASSPHRASE1EDIT, 75);
619 staticpassedit(&cp, "C&onfirm passphrase:",
620 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 75);
621 endbox(&cp);
622 beginbox(&cp, "Actions", IDC_BOX_ACTIONS);
623 staticbtn(&cp, "Generate a public/private key pair",
624 IDC_GENSTATIC, "&Generate", IDC_GENERATE);
625 staticbtn(&cp, "Load an existing private key file",
626 IDC_LOADSTATIC, "&Load", IDC_LOAD);
627 static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
628 "Save p&ublic key", IDC_SAVEPUB,
629 "&Save private key", IDC_SAVE);
630 endbox(&cp);
631 beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
632 radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
633 "SSH&1 (RSA)", IDC_KEYSSH1,
634 "SSH2 &RSA", IDC_KEYSSH2RSA,
635 "SSH2 &DSA", IDC_KEYSSH2DSA, NULL);
636 staticedit(&cp, "Number of &bits in a generated key:",
637 IDC_BITSSTATIC, IDC_BITS, 20);
638 endbox(&cp);
639 }
640 CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, IDC_KEYSSH1);
641 SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE);
642
643 /*
644 * Initially, hide the progress bar and the key display,
645 * and show the no-key display. Also disable the Save
646 * buttons, because with no key we obviously can't save
647 * anything.
648 */
649 hidemany(hwnd, nokey_ids, FALSE);
650 hidemany(hwnd, generating_ids, TRUE);
651 hidemany(hwnd, gotkey_ids, TRUE);
652 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
653 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
654
655 return 1;
656 case WM_MOUSEMOVE:
657 state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
658 if (state->collecting_entropy &&
659 state->entropy && state->entropy_got < state->entropy_required) {
660 state->entropy[state->entropy_got++] = lParam;
661 state->entropy[state->entropy_got++] = GetMessageTime();
662 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS,
663 state->entropy_got, 0);
664 if (state->entropy_got >= state->entropy_required) {
665 struct rsa_key_thread_params *params;
666 DWORD threadid;
667
668 /*
669 * Seed the entropy pool
670 */
671 random_add_heavynoise(state->entropy, state->entropy_size);
672 memset(state->entropy, 0, state->entropy_size);
673 sfree(state->entropy);
674 state->collecting_entropy = FALSE;
675
676 SetDlgItemText(hwnd, IDC_GENERATING, generating_msg);
677 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
678 MAKELPARAM(0, PROGRESSRANGE));
679 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
680
681 params = smalloc(sizeof(*params));
682 params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
683 params->dialog = hwnd;
684 params->keysize = state->keysize;
685 params->is_dsa = state->is_dsa;
686 params->key = &state->key;
687 params->dsskey = &state->dsskey;
688
689 if (!CreateThread(NULL, 0, generate_rsa_key_thread,
690 params, 0, &threadid)) {
691 MessageBox(hwnd, "Out of thread resources",
692 "Key generation error",
693 MB_OK | MB_ICONERROR);
694 sfree(params);
695 } else {
696 state->generation_thread_exists = TRUE;
697 }
698 }
699 }
700 break;
701 case WM_COMMAND:
702 switch (LOWORD(wParam)) {
703 case IDC_COMMENTEDIT:
704 if (HIWORD(wParam) == EN_CHANGE) {
705 state = (struct MainDlgState *)
706 GetWindowLong(hwnd, GWL_USERDATA);
707 if (state->key_exists) {
708 HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT);
709 int len = GetWindowTextLength(editctl);
710 if (*state->commentptr)
711 sfree(*state->commentptr);
712 *state->commentptr = smalloc(len + 1);
713 GetWindowText(editctl, *state->commentptr, len + 1);
714 if (state->ssh2) {
715 setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
716 &state->ssh2key);
717 } else {
718 setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
719 &state->key);
720 }
721 }
722 }
723 break;
724 case IDC_ABOUT:
725 EnableWindow(hwnd, 0);
726 DialogBox(hinst, MAKEINTRESOURCE(213), NULL, AboutProc);
727 EnableWindow(hwnd, 1);
728 SetActiveWindow(hwnd);
729 return 0;
730 case IDC_GIVEHELP:
731 if (HIWORD(wParam) == BN_CLICKED ||
732 HIWORD(wParam) == BN_DOUBLECLICKED) {
733 if (help_path) {
734 WinHelp(hwnd, help_path, HELP_COMMAND,
735 (DWORD)"JI(`',`puttygen.general')");
736 requested_help = TRUE;
737 }
738 }
739 return 0;
740 case IDC_GENERATE:
741 state =
742 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
743 if (!state->generation_thread_exists) {
744 BOOL ok;
745 state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
746 if (!ok)
747 state->keysize = DEFAULT_KEYSIZE;
748 /* If we ever introduce a new key type, check it here! */
749 state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
750 state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA);
751 if (state->keysize < 256) {
752 int ret = MessageBox(hwnd,
753 "PuTTYgen will not generate a key"
754 " smaller than 256 bits.\n"
755 "Key length reset to 256. Continue?",
756 "PuTTYgen Warning",
757 MB_ICONWARNING | MB_OKCANCEL);
758 if (ret != IDOK)
759 break;
760 state->keysize = 256;
761 SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
762 }
763 hidemany(hwnd, nokey_ids, TRUE);
764 hidemany(hwnd, generating_ids, FALSE);
765 hidemany(hwnd, gotkey_ids, TRUE);
766 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
767 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
768 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
769 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
770 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
771 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
772 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
773 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
774 state->key_exists = FALSE;
775 SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
776 state->collecting_entropy = TRUE;
777
778 /*
779 * My brief statistical tests on mouse movements
780 * suggest that there are about 2.5 bits of
781 * randomness in the x position, 2.5 in the y
782 * position, and 1.7 in the message time, making
783 * 5.7 bits of unpredictability per mouse movement.
784 * However, other people have told me it's far less
785 * than that, so I'm going to be stupidly cautious
786 * and knock that down to a nice round 2. With this
787 * method, we require two words per mouse movement,
788 * so with 2 bits per mouse movement we expect 2
789 * bits every 2 words.
790 */
791 state->entropy_required = (state->keysize / 2) * 2;
792 state->entropy_got = 0;
793 state->entropy_size = (state->entropy_required *
794 sizeof(*state->entropy));
795 state->entropy = smalloc(state->entropy_size);
796
797 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
798 MAKELPARAM(0, state->entropy_required));
799 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
800 }
801 break;
802 case IDC_SAVE:
803 state =
804 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
805 if (state->key_exists) {
806 char filename[FILENAME_MAX];
807 char passphrase[PASSPHRASE_MAXLEN];
808 char passphrase2[PASSPHRASE_MAXLEN];
809 GetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
810 passphrase, sizeof(passphrase));
811 GetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
812 passphrase2, sizeof(passphrase2));
813 if (strcmp(passphrase, passphrase2)) {
814 MessageBox(hwnd,
815 "The two passphrases given do not match.",
816 "PuTTYgen Error", MB_OK | MB_ICONERROR);
817 break;
818 }
819 if (!*passphrase) {
820 int ret;
821 ret = MessageBox(hwnd,
822 "Are you sure you want to save this key\n"
823 "without a passphrase to protect it?",
824 "PuTTYgen Warning",
825 MB_YESNO | MB_ICONWARNING);
826 if (ret != IDYES)
827 break;
828 }
829 if (prompt_keyfile(hwnd, "Save private key as:",
830 filename, 1)) {
831 int ret;
832 FILE *fp = fopen(filename, "r");
833 if (fp) {
834 char buffer[FILENAME_MAX + 80];
835 fclose(fp);
836 sprintf(buffer, "Overwrite existing file\n%.*s?",
837 FILENAME_MAX, filename);
838 ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
839 MB_YESNO | MB_ICONWARNING);
840 if (ret != IDYES)
841 break;
842 }
843 if (state->ssh2) {
844 ret = ssh2_save_userkey(filename, &state->ssh2key,
845 *passphrase ? passphrase :
846 NULL);
847 } else {
848 ret = saversakey(filename, &state->key,
849 *passphrase ? passphrase : NULL);
850 }
851 if (ret <= 0) {
852 MessageBox(hwnd, "Unable to save key file",
853 "PuTTYgen Error", MB_OK | MB_ICONERROR);
854 }
855 }
856 }
857 break;
858 case IDC_SAVEPUB:
859 state =
860 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
861 if (state->key_exists) {
862 char filename[FILENAME_MAX];
863 if (prompt_keyfile(hwnd, "Save public key as:",
864 filename, 1)) {
865 int ret;
866 FILE *fp = fopen(filename, "r");
867 if (fp) {
868 char buffer[FILENAME_MAX + 80];
869 fclose(fp);
870 sprintf(buffer, "Overwrite existing file\n%.*s?",
871 FILENAME_MAX, filename);
872 ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
873 MB_YESNO | MB_ICONWARNING);
874 if (ret != IDYES)
875 break;
876 }
877 if (state->ssh2) {
878 ret = save_ssh2_pubkey(filename, &state->ssh2key);
879 } else {
880 ret = save_ssh1_pubkey(filename, &state->key);
881 }
882 if (ret <= 0) {
883 MessageBox(hwnd, "Unable to save key file",
884 "PuTTYgen Error", MB_OK | MB_ICONERROR);
885 }
886 }
887 }
888 break;
889 case IDC_LOAD:
890 state =
891 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
892 if (!state->generation_thread_exists) {
893 char filename[FILENAME_MAX];
894 if (prompt_keyfile(hwnd, "Load private key:", filename, 0)) {
895 char passphrase[PASSPHRASE_MAXLEN];
896 int needs_pass;
897 int type, realtype;
898 int ret;
899 char *comment;
900 struct PassphraseProcStruct pps;
901 struct RSAKey newkey1;
902 struct ssh2_userkey *newkey2 = NULL;
903
904 type = realtype = key_type(filename);
905 if (type != SSH_KEYTYPE_SSH1 &&
906 type != SSH_KEYTYPE_SSH2 &&
907 !import_possible(type)) {
908 char msg[256];
909 sprintf(msg, "Couldn't load private key (%s)",
910 key_type_to_str(type));
911 MessageBox(NULL, msg,
912 "PuTTYgen Error", MB_OK | MB_ICONERROR);
913 break;
914 }
915
916 if (type != SSH_KEYTYPE_SSH1 &&
917 type != SSH_KEYTYPE_SSH2) {
918 realtype = type;
919 type = import_target_type(type);
920 }
921
922 comment = NULL;
923 if (realtype == SSH_KEYTYPE_SSH1)
924 needs_pass = rsakey_encrypted(filename, &comment);
925 else if (realtype == SSH_KEYTYPE_SSH2)
926 needs_pass =
927 ssh2_userkey_encrypted(filename, &comment);
928 else
929 needs_pass = import_encrypted(filename, realtype,
930 &comment);
931 pps.passphrase = passphrase;
932 pps.comment = comment;
933 do {
934 if (needs_pass) {
935 int dlgret;
936 dlgret = DialogBoxParam(hinst,
937 MAKEINTRESOURCE(210),
938 NULL, PassphraseProc,
939 (LPARAM) & pps);
940 if (!dlgret) {
941 ret = -2;
942 break;
943 }
944 } else
945 *passphrase = '\0';
946 if (type == SSH_KEYTYPE_SSH1) {
947 if (realtype == type)
948 ret = loadrsakey(filename, &newkey1,
949 passphrase);
950 else
951 ret = import_ssh1(filename, realtype,
952 &newkey1, passphrase);
953 } else {
954 if (realtype == type)
955 newkey2 = ssh2_load_userkey(filename,
956 passphrase);
957 else
958 newkey2 = import_ssh2(filename, realtype,
959 passphrase);
960 if (newkey2 == SSH2_WRONG_PASSPHRASE)
961 ret = -1;
962 else if (!newkey2)
963 ret = 0;
964 else
965 ret = 1;
966 }
967 } while (ret == -1);
968 if (comment)
969 sfree(comment);
970 if (ret == 0) {
971 MessageBox(NULL, "Couldn't load private key.",
972 "PuTTYgen Error", MB_OK | MB_ICONERROR);
973 } else if (ret == 1) {
974 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
975 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
976 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
977 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
978 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
979 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
980 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
981 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
982 /*
983 * Now update the key controls with all the
984 * key data.
985 */
986 {
987 SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
988 passphrase);
989 SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
990 passphrase);
991 if (type == SSH_KEYTYPE_SSH1) {
992 char buf[128];
993 char *savecomment;
994
995 state->ssh2 = FALSE;
996 state->commentptr = &state->key.comment;
997 state->key = newkey1;
998
999 /*
1000 * Set the key fingerprint.
1001 */
1002 savecomment = state->key.comment;
1003 state->key.comment = NULL;
1004 rsa_fingerprint(buf, sizeof(buf),
1005 &state->key);
1006 state->key.comment = savecomment;
1007
1008 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
1009 /*
1010 * Construct a decimal representation
1011 * of the key, for pasting into
1012 * .ssh/authorized_keys on a Unix box.
1013 */
1014 setupbigedit1(hwnd, IDC_KEYDISPLAY,
1015 IDC_PKSTATIC, &state->key);
1016 } else {
1017 char *fp;
1018 char *savecomment;
1019
1020 state->ssh2 = TRUE;
1021 state->commentptr =
1022 &state->ssh2key.comment;
1023 state->ssh2key = *newkey2; /* structure copy */
1024 sfree(newkey2);
1025
1026 savecomment = state->ssh2key.comment;
1027 state->ssh2key.comment = NULL;
1028 fp =
1029 state->ssh2key.alg->
1030 fingerprint(state->ssh2key.data);
1031 state->ssh2key.comment = savecomment;
1032
1033 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
1034 sfree(fp);
1035
1036 setupbigedit2(hwnd, IDC_KEYDISPLAY,
1037 IDC_PKSTATIC, &state->ssh2key);
1038 }
1039 SetDlgItemText(hwnd, IDC_COMMENTEDIT,
1040 *state->commentptr);
1041 }
1042 /*
1043 * Finally, hide the progress bar and show
1044 * the key data.
1045 */
1046 hidemany(hwnd, nokey_ids, TRUE);
1047 hidemany(hwnd, generating_ids, TRUE);
1048 hidemany(hwnd, gotkey_ids, FALSE);
1049 state->key_exists = TRUE;
1050 }
1051 }
1052 }
1053 break;
1054 }
1055 return 0;
1056 case WM_DONEKEY:
1057 state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
1058 state->generation_thread_exists = FALSE;
1059 state->key_exists = TRUE;
1060 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
1061 MAKELPARAM(0, PROGRESSRANGE));
1062 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
1063 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
1064 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
1065 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
1066 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
1067 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
1068 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
1069 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
1070 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
1071 if (state->ssh2) {
1072 if (state->is_dsa) {
1073 state->ssh2key.data = &state->dsskey;
1074 state->ssh2key.alg = &ssh_dss;
1075 } else {
1076 state->ssh2key.data = &state->key;
1077 state->ssh2key.alg = &ssh_rsa;
1078 }
1079 state->commentptr = &state->ssh2key.comment;
1080 } else {
1081 state->commentptr = &state->key.comment;
1082 }
1083 /*
1084 * Invent a comment for the key. We'll do this by including
1085 * the date in it. This will be so horrifyingly ugly that
1086 * the user will immediately want to change it, which is
1087 * what we want :-)
1088 */
1089 *state->commentptr = smalloc(30);
1090 {
1091 time_t t;
1092 struct tm *tm;
1093 time(&t);
1094 tm = localtime(&t);
1095 if (state->is_dsa)
1096 strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", tm);
1097 else
1098 strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", tm);
1099 }
1100
1101 /*
1102 * Now update the key controls with all the key data.
1103 */
1104 {
1105 char *savecomment;
1106 /*
1107 * Blank passphrase, initially. This isn't dangerous,
1108 * because we will warn (Are You Sure?) before allowing
1109 * the user to save an unprotected private key.
1110 */
1111 SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, "");
1112 SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, "");
1113 /*
1114 * Set the comment.
1115 */
1116 SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr);
1117 /*
1118 * Set the key fingerprint.
1119 */
1120 savecomment = *state->commentptr;
1121 *state->commentptr = NULL;
1122 if (state->ssh2) {
1123 char *fp;
1124 fp = state->ssh2key.alg->fingerprint(state->ssh2key.data);
1125 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
1126 sfree(fp);
1127 } else {
1128 char buf[128];
1129 rsa_fingerprint(buf, sizeof(buf), &state->key);
1130 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
1131 }
1132 *state->commentptr = savecomment;
1133 /*
1134 * Construct a decimal representation of the key, for
1135 * pasting into .ssh/authorized_keys or
1136 * .ssh/authorized_keys2 on a Unix box.
1137 */
1138 if (state->ssh2) {
1139 setupbigedit2(hwnd, IDC_KEYDISPLAY,
1140 IDC_PKSTATIC, &state->ssh2key);
1141 } else {
1142 setupbigedit1(hwnd, IDC_KEYDISPLAY,
1143 IDC_PKSTATIC, &state->key);
1144 }
1145 }
1146 /*
1147 * Finally, hide the progress bar and show the key data.
1148 */
1149 hidemany(hwnd, nokey_ids, TRUE);
1150 hidemany(hwnd, generating_ids, TRUE);
1151 hidemany(hwnd, gotkey_ids, FALSE);
1152 break;
1153 case WM_HELP:
1154 if (help_path) {
1155 int id = ((LPHELPINFO)lParam)->iCtrlId;
1156 char *cmd = NULL;
1157 switch (id) {
1158 case IDC_GENERATING:
1159 case IDC_PROGRESS:
1160 case IDC_GENSTATIC:
1161 case IDC_GENERATE:
1162 cmd = "JI(`',`puttygen.generate')"; break;
1163 case IDC_PKSTATIC:
1164 case IDC_KEYDISPLAY:
1165 cmd = "JI(`',`puttygen.pastekey')"; break;
1166 case IDC_FPSTATIC:
1167 case IDC_FINGERPRINT:
1168 cmd = "JI(`',`puttygen.fingerprint')"; break;
1169 case IDC_COMMENTSTATIC:
1170 case IDC_COMMENTEDIT:
1171 cmd = "JI(`',`puttygen.comment')"; break;
1172 case IDC_PASSPHRASE1STATIC:
1173 case IDC_PASSPHRASE1EDIT:
1174 case IDC_PASSPHRASE2STATIC:
1175 case IDC_PASSPHRASE2EDIT:
1176 cmd = "JI(`',`puttygen.passphrase')"; break;
1177 case IDC_LOADSTATIC:
1178 case IDC_LOAD:
1179 cmd = "JI(`',`puttygen.load')"; break;
1180 case IDC_SAVESTATIC:
1181 case IDC_SAVE:
1182 cmd = "JI(`',`puttygen.savepriv')"; break;
1183 case IDC_SAVEPUB:
1184 cmd = "JI(`',`puttygen.savepub')"; break;
1185 case IDC_TYPESTATIC:
1186 case IDC_KEYSSH1:
1187 case IDC_KEYSSH2RSA:
1188 case IDC_KEYSSH2DSA:
1189 cmd = "JI(`',`puttygen.keytype')"; break;
1190 case IDC_BITSSTATIC:
1191 case IDC_BITS:
1192 cmd = "JI(`',`puttygen.bits')"; break;
1193 }
1194 if (cmd) {
1195 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1196 requested_help = TRUE;
1197 } else {
1198 MessageBeep(0);
1199 }
1200 }
1201 break;
1202 case WM_CLOSE:
1203 state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
1204 sfree(state);
1205 if (requested_help) {
1206 WinHelp(hwnd, help_path, HELP_QUIT, 0);
1207 requested_help = FALSE;
1208 }
1209 EndDialog(hwnd, 1);
1210 return 0;
1211 }
1212 return 0;
1213 }
1214
1215 void cleanup_exit(int code) { exit(code); }
1216
1217 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1218 {
1219 InitCommonControls();
1220 hinst = inst;
1221
1222 /*
1223 * See if we can find our Help file.
1224 */
1225 {
1226 char b[2048], *p, *q, *r;
1227 FILE *fp;
1228 GetModuleFileName(NULL, b, sizeof(b) - 1);
1229 r = b;
1230 p = strrchr(b, '\\');
1231 if (p && p >= r) r = p+1;
1232 q = strrchr(b, ':');
1233 if (q && q >= r) r = q+1;
1234 strcpy(r, "putty.hlp");
1235 if ( (fp = fopen(b, "r")) != NULL) {
1236 help_path = dupstr(b);
1237 fclose(fp);
1238 } else
1239 help_path = NULL;
1240 }
1241
1242 random_init();
1243 return DialogBox(hinst, MAKEINTRESOURCE(201), NULL,
1244 MainDlgProc) != IDOK;
1245 }