From d50c7805c1a5b0eaf38192c55770084bcbd5dfdb Mon Sep 17 00:00:00 2001 From: Selva Nair Date: Wed, 29 Mar 2023 22:47:21 -0400 Subject: [PATCH] Implement Password Reveal Feature - Only "hot" user input -- i.e, freshly typed password starting from an empty string, with keyboard focus still in the edit box -- can be revealed. In particular, prefilled passwod (from cached value) cannot be revealed. - Once keyboard focus moves out of the password edit box, the inpit has to be deleted for the reveal feature to get re-enabled. Signed-off-by: Selva Nair --- Makefile.am | 2 ++ as.c | 25 ++++++++++++++-- misc.c | 61 ++++++++++++++++++++++++++++++++++++++ misc.h | 3 ++ openvpn-gui-res.h | 3 ++ openvpn.c | 34 +++++++++++++++++++-- plap/Makefile.am | 2 ++ plap/openvpn-plap-res.rc | 2 ++ res/eye-stroke.ico | Bin 0 -> 1150 bytes res/eye.ico | Bin 0 -> 1150 bytes res/openvpn-gui-res-en.rc | 16 ++++++---- res/openvpn-gui-res.rc | 2 ++ 12 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 res/eye-stroke.ico create mode 100644 res/eye.ico diff --git a/Makefile.am b/Makefile.am index 6307759..8a0c1c9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,8 @@ openvpn_gui_RESOURCES = \ res/disconnected.ico \ res/openvpn-gui.ico \ res/reconnecting.ico \ + res/eye.ico \ + res/eye-stroke.ico \ res/openvpn-gui.manifest \ res/tileimage.bmp diff --git a/as.c b/as.c index 6028688..9c23648 100644 --- a/as.c +++ b/as.c @@ -248,6 +248,8 @@ CRDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) /* disable OK button by default - not disabled in resources */ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), 0); break; case WM_COMMAND: @@ -255,6 +257,11 @@ CRDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) switch (LOWORD(wParam)) { case ID_EDT_RESPONSE: + if (!(param->flags & FLAG_CR_ECHO)) + { + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + } if (HIWORD(wParam) == EN_UPDATE) { /* enable OK if response is non-empty */ BOOL enableOK = GetWindowTextLength((HWND)lParam); @@ -272,6 +279,11 @@ CRDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) case IDCANCEL: EndDialog(hwndDlg, LOWORD(wParam)); return TRUE; + + case ID_PASSWORD_REVEAL: /* password reveal symbol clicked */ + ChangePasswordVisibility(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + return TRUE; } break; @@ -624,15 +636,19 @@ ImportProfileFromURLDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa } /* disable OK button until required data is filled in */ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); - + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), 0); break; case WM_COMMAND: type = (server_type_t) GetProp(hwndDlg, cfgProp); switch (LOWORD(wParam)) { - case ID_EDT_AUTH_USER: case ID_EDT_AUTH_PASS: + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + /* fall through */ + case ID_EDT_AUTH_USER: case ID_EDT_URL: if (HIWORD(wParam) == EN_UPDATE) { /* enable OK button only if url and username are filled */ @@ -690,6 +706,11 @@ ImportProfileFromURLDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa case IDCANCEL: EndDialog(hwndDlg, LOWORD(wParam)); return TRUE; + + case ID_PASSWORD_REVEAL: /* password reveal symbol clicked */ + ChangePasswordVisibility(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + return TRUE; } break; diff --git a/misc.c b/misc.c index 69363c0..8fe7c9e 100644 --- a/misc.c +++ b/misc.c @@ -1083,3 +1083,64 @@ out: CryptReleaseContext(cp, 0); return retval; } + +/* Setup Password reveal + * Inputs: edit handle to password edit control + * btn handle to the control that toggles the reveal + * wParam action being handled, or 0 for init + */ +void +ResetPasswordReveal(HWND edit, HWND btn, WPARAM wParam) +{ + if (!edit || !btn) + { + return; + } + /* set the password field to be masked as a sane default */ + SendMessage(edit, EM_SETPASSWORDCHAR, (WPARAM)'*', 0); + SendMessage(btn, STM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM)LoadLocalizedSmallIcon(ID_ICO_EYE)); + + /* if password is not masked on init, disable reveal "button" */ + if (wParam == 0 && SendMessage(edit, EM_GETPASSWORDCHAR, 0, 0) == 0) + { + ShowWindow(btn, SW_HIDE); + } + /* on losing focus disable password reveal button */ + else if (HIWORD(wParam) == EN_KILLFOCUS) + { + ShowWindow(btn, SW_HIDE); + } + /* if/when password is cleared enable/re-enable reveal button */ + else if (GetWindowTextLength(edit) == 0) + { + ShowWindow(btn, SW_SHOW); + } +} + +/* Toggle masking of text in password field + * Inputs: edit handle to password edit control + * btn handle to the control that toggles the reveal + * wParam action being handled + */ +void +ChangePasswordVisibility(HWND edit, HWND btn, WPARAM wParam) +{ + if (!edit || !btn) + { + return; + } + if (HIWORD(wParam) == STN_CLICKED) + { + if (SendMessage(edit, EM_GETPASSWORDCHAR, 0, 0) == 0) /* currently visible */ + { + SendMessage(edit, EM_SETPASSWORDCHAR, (WPARAM)'*', 0); + SendMessage(btn, STM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM)LoadLocalizedSmallIcon(ID_ICO_EYE)); + } + else + { + SendMessage(edit, EM_SETPASSWORDCHAR, 0, 0); + SendMessage(btn, STM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM)LoadLocalizedSmallIcon(ID_ICO_EYESTROKE)); + } + InvalidateRect(edit, NULL, TRUE); /* without this the control doesn't seem to get redrawn promptly */ + } +} diff --git a/misc.h b/misc.h index 1315d5a..2e5f093 100644 --- a/misc.h +++ b/misc.h @@ -156,4 +156,7 @@ bool OVPNMsgWait(DWORD timeout, HWND hdlg); bool GetRandomPassword(char *buf, size_t len); +void ResetPasswordReveal(HWND edit, HWND btn, WPARAM wParam); +void ChangePasswordVisibility(HWND edit, HWND btn, WPARAM wParam); + #endif diff --git a/openvpn-gui-res.h b/openvpn-gui-res.h index fcc45dd..149bac2 100644 --- a/openvpn-gui-res.h +++ b/openvpn-gui-res.h @@ -27,6 +27,8 @@ #define ID_ICO_CONNECTED 91 #define ID_ICO_CONNECTING 92 #define ID_ICO_DISCONNECTED 93 +#define ID_ICO_EYE 94 +#define ID_ICO_EYESTROKE 95 /* About Dialog */ #define ID_DLG_ABOUT 100 @@ -39,6 +41,7 @@ #define ID_DLG_PASSPHRASE 150 #define ID_EDT_PASSPHRASE 151 #define ID_LTEXT_PASSWORD 152 +#define ID_PASSWORD_REVEAL 153 /* Status Dialog */ #define ID_DLG_STATUS 160 diff --git a/openvpn.c b/openvpn.c index febdd49..5c2e4f5 100644 --- a/openvpn.c +++ b/openvpn.c @@ -575,7 +575,8 @@ UserAuthDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) ForceForegroundWindow(hwndDlg); else SetForegroundWindow(hwndDlg); - + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), 0); break; case WM_LBUTTONDOWN: @@ -590,8 +591,11 @@ UserAuthDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) param = (auth_param_t *) GetProp(hwndDlg, cfgProp); switch (LOWORD(wParam)) { - case ID_EDT_AUTH_USER: case ID_EDT_AUTH_PASS: + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + /* fall through */ + case ID_EDT_AUTH_USER: case ID_EDT_AUTH_CHALLENGE: if (HIWORD(wParam) == EN_UPDATE) { @@ -654,6 +658,11 @@ UserAuthDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) EndDialog(hwndDlg, LOWORD(wParam)); StopOpenVPN(param->c); return TRUE; + + case ID_PASSWORD_REVEAL: /* password reveal symbol clicked */ + ChangePasswordVisibility(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + return TRUE; } break; @@ -771,6 +780,8 @@ GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) /* disable OK button until response is filled-in */ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); } + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), 0); break; @@ -781,6 +792,11 @@ GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) switch (LOWORD(wParam)) { case ID_EDT_RESPONSE: + if (!(param->flags & FLAG_CR_ECHO)) + { + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + } if (HIWORD(wParam) == EN_UPDATE) { /* enable OK if response is non-empty */ @@ -854,6 +870,11 @@ GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) EndDialog(hwndDlg, LOWORD(wParam)); StopOpenVPN(param->c); return TRUE; + + case ID_PASSWORD_REVEAL: /* password reveal symbol clicked */ + ChangePasswordVisibility(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + return TRUE; } break; @@ -926,6 +947,8 @@ PrivKeyPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) /* disable OK button by default - not disabled in resources */ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_PASSPHRASE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), 0); break; case WM_COMMAND: @@ -944,6 +967,8 @@ PrivKeyPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) break; case ID_EDT_PASSPHRASE: + ResetPasswordReveal(GetDlgItem(hwndDlg, ID_EDT_PASSPHRASE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); if (HIWORD(wParam) == EN_UPDATE) { /* enable OK if response is non-empty */ @@ -975,6 +1000,11 @@ PrivKeyPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) EndDialog(hwndDlg, LOWORD(wParam)); StopOpenVPN (c); return TRUE; + + case ID_PASSWORD_REVEAL: /* password reveal symbol clicked */ + ChangePasswordVisibility(GetDlgItem(hwndDlg, ID_EDT_PASSPHRASE), + GetDlgItem(hwndDlg, ID_PASSWORD_REVEAL), wParam); + return TRUE; } break; diff --git a/plap/Makefile.am b/plap/Makefile.am index b9a4320..50fe432 100644 --- a/plap/Makefile.am +++ b/plap/Makefile.am @@ -67,6 +67,8 @@ libopenvpn_plap_la_RESOURCES = \ $(top_srcdir)/res/disconnected.ico \ $(top_srcdir)/res/openvpn-gui.ico \ $(top_srcdir)/res/reconnecting.ico \ + $(top_srcdir)/res/eye.ico \ + $(top_srcdir)/res/eye-stroke.ico \ openvpn-plap-res.rc \ openvpn-plap.manifest diff --git a/plap/openvpn-plap-res.rc b/plap/openvpn-plap-res.rc index 1dc7726..86e7885 100644 --- a/plap/openvpn-plap-res.rc +++ b/plap/openvpn-plap-res.rc @@ -39,6 +39,8 @@ ID_ICO_APP ICON DISCARDABLE "../res/openvpn-gui.ico" ID_ICO_CONNECTED ICON DISCARDABLE "../res/connected.ico" ID_ICO_CONNECTING ICON DISCARDABLE "../res/connecting.ico" ID_ICO_DISCONNECTED ICON DISCARDABLE "../res/disconnected.ico" +ID_ICO_EYE ICON DISCARDABLE "../res/eye.ico" +ID_ICO_EYESTROKE ICON DISCARDABLE "../res/eye-stroke.ico" IDB_TILE_IMAGE BITMAP DISCARDABLE "../res/tileimage.bmp" diff --git a/res/eye-stroke.ico b/res/eye-stroke.ico new file mode 100644 index 0000000000000000000000000000000000000000..74d23ac1deaccb73824c70d316f816081035674e GIT binary patch literal 1150 zcmdr~t8Rrr5S>dggeFB**AV&<_k*es7;HTbjlh9KAQ270;rInC3D-4*gz21Zmuxmy zRW*g29d>rkna4tOkv9lP))#tniJpn*RUj_$eEY=^$(j8yGyNs3>ni2^z9#99%d+HQ z7`Un`1$@u*3|%~T``!1Qi=trP_Zb>M6h$0{;kgfjK*b=|)EUQ-JZG@7PSzC6!)+qP3zbJ0VVWeknuI8+Q`p`*D;lAJtf0yqcPwB~HV z!CBXJ%G&vn4|%l)>ap{Ki(Gd9%d+q^O{!+((7mK-s&bkA>w5M4F=KE5%pcHu*f@^a zp1;=kvFfhx-GAG*O3R#s#l8D){O=eUs!ZimaC;*9cp!Se5IxGUud={m5C2VoSQB6J CcERuf literal 0 HcmV?d00001 diff --git a/res/eye.ico b/res/eye.ico new file mode 100644 index 0000000000000000000000000000000000000000..7857fe4a9d1026242ab525d33abc4caaee3b3213 GIT binary patch literal 1150 zcmdr~F>ZrE5F9HsNRe_?>NE&HA`f!S3(}K{njT6XKw%*|I=(;)!Yx8#v$HlRvh2q0 zd}!F)*_j3Q0Csvg2fcsc-~pci_6ntxIrAC-tz9n>!HYq`4YlmEdZQ@XeC$cEPA~V2%*S!GoE$ym%CfZh$n)uC zuV=oWe&XKzc?Nt>;@