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