From b66c56f44129817540086c1c64e4d3e6631c31f4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:41:08 +0100 Subject: [PATCH] Windows PuTTYgen: also display certificate info. When PuTTYgen is holding a certified key, I don't think there's any sensible use for pasting around the full public key in authorized_keys format, because the whole point is that what you put in authorized_keys is 'please trust this CA' rather than the specific key. So instead I've reused the space in the dialog box to indicate that it's a certificate, and provide a 'more info' sub-dialog. --- windows/platform.h | 1 + windows/puttygen.c | 229 +++++++++++++++++++++++++++++++++++++++----- windows/puttygen.rc | 10 ++ 3 files changed, 214 insertions(+), 26 deletions(-) diff --git a/windows/platform.h b/windows/platform.h index 1e0cd1bc..60b8db77 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -444,6 +444,7 @@ void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...); void bareradioline(struct ctlpos *cp, int nacross, ...); void radiobig(struct ctlpos *cp, const char *text, int id, ...); void checkbox(struct ctlpos *cp, const char *text, int id); +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn); void statictext(struct ctlpos *cp, const char *text, int lines, int id); void staticbtn(struct ctlpos *cp, const char *stext, int sid, const char *btext, int bid); diff --git a/windows/puttygen.c b/windows/puttygen.c index db7c5c87..c7b39c97 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -696,6 +696,7 @@ enum { IDC_GENERATING, IDC_PROGRESS, IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, @@ -723,23 +724,44 @@ enum { IDC_ADDCERT, IDC_REMCERT, }; -static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) +static void setupbigedit1(HWND hwnd, RSAKey *key) { - char *buffer = ssh1_pubkey_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into authorized_keys file:"); + + char *buffer = ssh1_pubkey_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); sfree(buffer); } -static void setupbigedit2(HWND hwnd, int id, int idstatic, - ssh2_userkey *key) +static void setupbigedit2(HWND hwnd, ssh2_userkey *key) { - char *buffer = ssh2_pubkey_openssh_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " - "OpenSSH authorized_keys file:"); - sfree(buffer); + if (ssh_key_alg(key->key)->is_certificate) { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_HIDE); + + SetDlgItemText(hwnd, IDC_CERTSTATIC, + "This public key contains an OpenSSH certificate."); + } else { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + + char *buffer = ssh2_pubkey_openssh_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); + sfree(buffer); + } } /* @@ -764,13 +786,16 @@ void old_keyfile_warning(void) 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, +static const int gotkey_ids_unconditional[] = { IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0 }; +static const int gotkey_ids_conditional[] = { + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, +}; /* * Small UI helper function to switch the state of the main dialog @@ -784,7 +809,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 0: /* no key */ hidemany(hwnd, nokey_ids, false); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -819,7 +845,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 1: /* generating key */ hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, false); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -854,7 +881,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 2: hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, false); + hidemany(hwnd, gotkey_ids_unconditional, false); + // gotkey_ids_conditional will be unhidden by setupbigedit2 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); @@ -1042,7 +1070,7 @@ static void update_ui_after_ssh2_pubkey_change( SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); sfree(fp); - setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, @@ -1073,7 +1101,7 @@ static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, * Construct a decimal representation of the key, for pasting * into .ssh/authorized_keys on a Unix box. */ - setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->key); + setupbigedit1(hwnd, &state->key); } else { state->ssh2 = true; state->commentptr = &state->ssh2key.comment; @@ -1340,6 +1368,130 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) } } +/* + * Dialog-box function and context structure for the 'Certificate + * info' button. + */ +struct certinfo_dialog_ctx { + SeatDialogText *text; +}; + +static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) +{ + struct certinfo_dialog_ctx *ctx = (struct certinfo_dialog_ctx *)vctx; + + switch (msg) { + case WM_INITDIALOG: { + int index = 100, y = 12; + + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); + + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 426; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 426; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 130; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 150; + rv.right = 438; + rv.top = y; + rv.bottom = 12; + + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } + + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; + case WM_CLOSE: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; +} + /* * Dialog-box function for the main PuTTYgen dialog box. */ @@ -1469,6 +1621,18 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, cp2 = cp; statictext(&cp2, "", 1, IDC_GENERATING); progressbar(&cp2, IDC_PROGRESS); + cp2 = cp; + bigeditctrl(&cp2, NULL, -1, IDC_CERTSTATIC, 3); + { + HWND child = GetDlgItem(hwnd, IDC_CERTSTATIC); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + style &= ~WS_VSCROLL; + SetWindowLongPtr(child, GWL_STYLE, style); + SendMessage(child, EM_SETREADONLY, true, 0); + } + MakeDlgItemBorderless(hwnd, IDC_CERTSTATIC); + cp2.xoff = cp2.width = cp2.width / 3; + button(&cp2, "Certificate info...", IDC_CERTMOREINFO, false); bigeditctrl(&cp, "&Public key for pasting into authorized_keys file:", IDC_PKSTATIC, IDC_KEYDISPLAY, 5); @@ -1695,11 +1859,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, *state->commentptr = snewn(len + 1, char); GetWindowText(editctl, *state->commentptr, len + 1); if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->key); + setupbigedit1(hwnd, &state->key); } } } @@ -2039,6 +2201,23 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, remove_certificate(hwnd, state); } break; + case IDC_CERTMOREINFO: { + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (!state->key_exists || !state->ssh2 || !state->ssh2key.key) + break; + if (!ssh_key_alg(state->ssh2key.key)->is_certificate) + break; + + struct certinfo_dialog_ctx ctx[1]; + ctx->text = ssh_key_cert_info(state->ssh2key.key); + ShinyDialogBox(hinst, MAKEINTRESOURCE(216), + "PuTTYgenCertInfo", hwnd, CertInfoProc, ctx); + seat_dialog_text_free(ctx->text); + break; + } } return 0; case WM_DONEKEY: @@ -2116,11 +2295,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * .ssh/authorized_keys2 on a Unix box. */ if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->key); + setupbigedit1(hwnd, &state->key); } } /* diff --git a/windows/puttygen.rc b/windows/puttygen.rc index d1cef50d..fbe14fc3 100644 --- a/windows/puttygen.rc +++ b/windows/puttygen.rc @@ -82,6 +82,16 @@ BEGIN PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14 END +/* Accelerators used: clw */ +216 DIALOG DISCARDABLE 140, 40, 450, 300 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen: certificate information" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYgenCertInfo" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14 +END + #include "version.rc2" #ifndef NO_MANIFESTS