From ff391d9a6afb856e20e827edbad0aff9e6caffe3 Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Mon, 6 Jul 2020 18:00:25 +0200 Subject: Windows: Support direct password drag-n-drop from external applications (e.g. KeePass) which is more secure than using clipboard. --- src/Common/Dlgcode.c | 422 +++++++++++++++++++++++++++++++++++++++++++ src/Common/Dlgcode.h | 55 ++++++ src/ExpandVolume/WinMain.cpp | 24 +++ src/Format/Tcformat.c | 28 ++- src/Mount/Mount.c | 70 +++++++ 5 files changed, 597 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/Common/Dlgcode.c b/src/Common/Dlgcode.c index bd1f6f84..0203a931 100644 --- a/src/Common/Dlgcode.c +++ b/src/Common/Dlgcode.c @@ -11638,6 +11638,17 @@ BOOL CALLBACK SecurityTokenPasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wPara SetForegroundWindow (hwndDlg); SetFocus (GetDlgItem (hwndDlg, IDC_TOKEN_PASSWORD)); + + if (!bSecureDesktopOngoing) + { + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; + } } return 0; @@ -11673,6 +11684,19 @@ BOOL CALLBACK SecurityTokenPasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wPara EndDialog (hwndDlg, lw); } return 1; + + case WM_NCDESTROY: + { + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; } return 0; @@ -14474,3 +14498,401 @@ BitLockerEncryptionStatus GetBitLockerEncryptionStatus(WCHAR driveLetter) CoUninitialize(); return blStatus; } + +//////////////////////////////////////////////////////////////////////////////////////// + +static CLIPFORMAT g_supportedFormats[] = { CF_UNICODETEXT, CF_TEXT, CF_OEMTEXT}; + +//************************************************************* +// GenericDropTarget +//************************************************************* +GenericDropTarget::GenericDropTarget(CLIPFORMAT* pFormats, size_t count) + : m_DropTargetWnd(NULL), + m_dwRefCount(1), + m_KeyState(0L), + m_Data(NULL) +{ + m_DropPoint.x = 0; + m_DropPoint.y = 0; + + if (pFormats && count) + { + for (size_t i = 0; i < count; i++) + { + m_SupportedFormat.push_back (pFormats[i]); + } + } +} + +GenericDropTarget::~GenericDropTarget() +{ +} + +HRESULT GenericDropTarget::QueryInterface(REFIID iid, void **ppvObject) +{ + if(ppvObject == NULL) + return E_FAIL; + + if (iid == IID_IUnknown) + { + AddRef(); + (*ppvObject) = this; + return S_OK; + } + // compare guids fast and dirty + if (IsEqualGUID (iid, IID_IDropTarget)) + { + AddRef(); + (*ppvObject) = this; + return S_OK; + } + + return E_FAIL; +} + +ULONG GenericDropTarget::AddRef(void) +{ + return (ULONG) InterlockedIncrement (&m_dwRefCount); +} + +ULONG GenericDropTarget::Release(void) +{ + if (InterlockedDecrement (&m_dwRefCount) == 0) + { + delete this; + return 0; + } + else + return (ULONG) m_dwRefCount; +} + +//************************************************************* +// Register +// Called by whom implements us so we can serve +//************************************************************* +BOOL GenericDropTarget::Register(HWND hWnd) +{ + if(NULL == hWnd) + return E_FAIL; + + OleInitialize(NULL); + + // required: these MUST be strong locked + CoLockObjectExternal(this, TRUE, 0); + + // this is ok, we have it + DWORD hRes = ::RegisterDragDrop(hWnd, this); + if(SUCCEEDED(hRes)) + { + // keep + m_DropTargetWnd = hWnd; + return TRUE; + } + + // unlock + CoLockObjectExternal(this, FALSE, 0); + + // bye bye COM + OleUninitialize(); + + // wont accept data now + return FALSE; +} + +//************************************************************* +// Revoke +// Unregister us as a target +//************************************************************* +void GenericDropTarget::Revoke() +{ + if(NULL == m_DropTargetWnd) + return; + + RevokeDragDrop(m_DropTargetWnd); + + m_DropTargetWnd = NULL; + + // unlock + CoLockObjectExternal(this, FALSE, 0); + + // bye bye COM + OleUninitialize(); +} + +//************************************************************* +// DragEnter +//************************************************************* +HRESULT GenericDropTarget::DragEnter(struct IDataObject *pDataObject, unsigned long grfKeyState, struct _POINTL pMouse, unsigned long * pDropEffect) +{ + if(pDataObject == NULL) + return E_FAIL; // must have data + + // keep point + m_DropPoint.x = pMouse.x; + m_DropPoint.y = pMouse.y; + + // keep key + m_KeyState = grfKeyState; + + // call top + *pDropEffect = GotEnter(); + + return S_OK; +} + +//************************************************************* +// DragOver +// Coming over! +//************************************************************* +HRESULT GenericDropTarget::DragOver(unsigned long grfKeyState, struct _POINTL pMouse, unsigned long *pEffect) +{ + // keep point + m_DropPoint.x = pMouse.x; + m_DropPoint.y = pMouse.y; + + // keep key + m_KeyState = grfKeyState; + + // call top + *pEffect = GotDrag(); + + return S_OK; +} + +//************************************************************* +// DragLeave +// Free! At last! +//************************************************************* +HRESULT GenericDropTarget::DragLeave(void) +{ + GotLeave(); + + return S_OK; +} + +//************************************************************* +// Drop +//************************************************************* +HRESULT GenericDropTarget::Drop(struct IDataObject *pDataObject, unsigned long grfKeyState, struct _POINTL pMouse, unsigned long *pdwEffect) +{ + if(NULL == pDataObject) + return E_FAIL; + + // do final effect + *pdwEffect = DROPEFFECT_COPY; + + // Check the data + FORMATETC iFormat; + ZeroMemory(&iFormat, sizeof(FORMATETC)); + + STGMEDIUM iMedium; + ZeroMemory(&iMedium, sizeof(STGMEDIUM)); + + HRESULT hRes; + size_t i; + bool bFound = false; + + for (i = 0; i < m_SupportedFormat.size(); i++) + { + // data + iFormat.cfFormat = m_SupportedFormat[i]; + iFormat.dwAspect = DVASPECT_CONTENT; + iFormat.lindex = -1; // give me all baby + iFormat.tymed = TYMED_HGLOBAL; // want mem + + hRes = pDataObject->GetData(&iFormat, &iMedium); + if(SUCCEEDED(hRes)) + { + bFound = true; + break; + } + } + + if (!bFound) + return hRes; + + // we have the data, get it + BYTE *iMem = (BYTE *)::GlobalLock(iMedium.hGlobal); + + // pass over + m_Data = iMem; + + // keep point + m_DropPoint.x = pMouse.x; + m_DropPoint.y = pMouse.y; + + // keep key + m_KeyState = grfKeyState; + + // notify parent of drop + GotDrop(m_SupportedFormat[i]); + + ::GlobalUnlock(iMedium.hGlobal); + + // free data + if(iMedium.pUnkForRelease != NULL) + iMedium.pUnkForRelease->Release(); + + return S_OK; +} + +//************************************************************* +// Stub implementation +// Real stuff would be done in parent +//************************************************************* +void GenericDropTarget::GotDrop(CLIPFORMAT format) +{ +} + +DWORD GenericDropTarget::GotDrag(void) +{ + return DROPEFFECT_LINK; +} + +void GenericDropTarget::GotLeave(void) +{ +} + +DWORD GenericDropTarget::GotEnter(void) +{ + return DROPEFFECT_LINK; +} + +// ************************************************************ +// PasswordEditDropTarget +// Constructor +// ************************************************************ +PasswordEditDropTarget::PasswordEditDropTarget() : GenericDropTarget (g_supportedFormats, ARRAYSIZE (g_supportedFormats)) +{ + +} + +// ************************************************************ +// GotDrag + +// ************************************************************ +DWORD PasswordEditDropTarget::GotDrag(void) +{ + return GotEnter(); +} + +// ************************************************************ +// GotLeave +// ************************************************************ +void PasswordEditDropTarget::GotLeave(void) +{ +} + +// ************************************************************ +// GotEnter +// ************************************************************ +DWORD PasswordEditDropTarget::GotEnter(void) +{ + TCHAR szClassName[64]; + DWORD dwStyles; + int maxLen; + HWND hChild = WindowFromPoint (m_DropPoint); + // check that we are on password edit control (we use maximum length to correctly identify password fields since they don't always have ES_PASSWORD style (if the the user checked show password) + if (hChild && GetClassName (hChild, szClassName, ARRAYSIZE (szClassName)) && (0 == _tcsicmp (szClassName, _T("EDIT"))) + && (dwStyles = GetWindowLong (hChild, GWL_STYLE)) && !(dwStyles & ES_NUMBER) + && (maxLen = (int) SendMessage (hChild, EM_GETLIMITTEXT, 0, 0)) && (maxLen == MAX_PASSWORD || maxLen == MAX_LEGACY_PASSWORD) + ) + { + return DROPEFFECT_COPY; + } + + return DROPEFFECT_LINK; +} + +// ************************************************************ +// GotDrop +// Called if we have a drop text drop here. +// +// ************************************************************ +void PasswordEditDropTarget::GotDrop(CLIPFORMAT format) +{ + // value contains the material itself + if(m_Data) + { + TCHAR szClassName[64]; + DWORD dwStyles; + int maxLen; + HWND hChild = WindowFromPoint (m_DropPoint); + if (hChild && GetClassName (hChild, szClassName, ARRAYSIZE (szClassName)) && (0 == _tcsicmp (szClassName, _T("EDIT"))) + && (dwStyles = GetWindowLong (hChild, GWL_STYLE)) && !(dwStyles & ES_NUMBER) + && (maxLen = (int) SendMessage (hChild, EM_GETLIMITTEXT, 0, 0)) && (maxLen == MAX_PASSWORD || maxLen == MAX_LEGACY_PASSWORD) + ) + { + WCHAR* wszText; + int wlen; + bool bFree = false; + // get the text + if (format == CF_UNICODETEXT) + { + wszText = (WCHAR *)m_Data; + } + else + { + char *iText = (char *)m_Data; + wlen = MultiByteToWideChar ((format == CF_OEMTEXT)? CP_OEMCP : CP_ACP, 0, iText, -1, NULL, 0); + wszText = new WCHAR[wlen]; + if (wszText) + { + wlen = MultiByteToWideChar (CP_ACP, 0, iText, -1, wszText, wlen); + bFree = true; + } + } + + WCHAR* pchData = wszText; + int txtlen = 0; + bool bTruncated = false; + + // remove any appended \r or \n + while (*pchData) + { + if (*pchData == '\r' || *pchData == '\n') + break; + else + { + txtlen++; + pchData++; + } + } + + if (txtlen) + { + if (txtlen > maxLen) + { + bTruncated = true; + txtlen = maxLen; + } + + SetFocus (hChild); + + wszText[txtlen] = 0; + SetWindowText(hChild , wszText); + + if (bTruncated) + { + EDITBALLOONTIP ebt; + + ebt.cbStruct = sizeof( EDITBALLOONTIP ); + ebt.pszText = GetString ("PASSWORD_PASTED_TRUNCATED"); + ebt.pszTitle = lpszTitle; + ebt.ttiIcon = TTI_WARNING_LARGE; // tooltip warning icon + + SendMessage(hChild, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt); + + MessageBeep (0xFFFFFFFF); + } + } + + if (bFree) + { + burn (wszText, wlen * sizeof (WCHAR)); + delete [] wszText; + } + } + } +} + diff --git a/src/Common/Dlgcode.h b/src/Common/Dlgcode.h index 9e77c0a9..52d94f10 100644 --- a/src/Common/Dlgcode.h +++ b/src/Common/Dlgcode.h @@ -691,6 +691,61 @@ typedef void (CALLBACK* WaitThreadProc)(void* pArg, HWND hWaitDlg); void BringToForeground(HWND hWnd); void ShowWaitDialog(HWND hwnd, BOOL bUseHwndAsParent, WaitThreadProc callback, void* pArg); +// classes used to implement support for password drag-n-drop from KeePass Password Safe +// Implementation based the following source code with many modifications to fix isses and add features +// URL: https://www.codeguru.com/cpp/misc/misc/draganddrop/article.php/c349/Drag-And-Drop-between-Window-Controls.htm + +interface GenericDropTarget : public IDropTarget +{ +public: + GenericDropTarget(CLIPFORMAT* pFormats, size_t count); + ~GenericDropTarget(); + + // basic IUnknown stuff + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject); + ULONG STDMETHODCALLTYPE AddRef(void); + ULONG STDMETHODCALLTYPE Release(void); + + HRESULT STDMETHODCALLTYPE DragEnter(struct IDataObject *,unsigned long,struct _POINTL,unsigned long *); + HRESULT STDMETHODCALLTYPE DragOver(unsigned long,struct _POINTL,unsigned long *); + HRESULT STDMETHODCALLTYPE DragLeave(void); + HRESULT STDMETHODCALLTYPE Drop(struct IDataObject *,unsigned long,struct _POINTL,unsigned long *); + + // called by parents + BOOL Register(HWND hWnd); + void Revoke(); + + // call parent we have goodies + virtual void GotDrop(CLIPFORMAT format); + virtual DWORD GotDrag(void); + virtual void GotLeave(void); + virtual DWORD GotEnter(void); +public: + BYTE *m_Data; + + POINT m_DropPoint; + + DWORD m_KeyState; + +protected: + HWND m_DropTargetWnd; + std::vector m_SupportedFormat; + volatile LONG m_dwRefCount; +}; + +class PasswordEditDropTarget : public GenericDropTarget +{ +public: + PasswordEditDropTarget(); + + // called by child we have drop + void GotDrop(CLIPFORMAT format); + DWORD GotDrag(void); + void GotLeave(void); + DWORD GotEnter(void); +}; + + #endif // __cplusplus #endif // TC_HEADER_DLGCODE diff --git a/src/ExpandVolume/WinMain.cpp b/src/ExpandVolume/WinMain.cpp index 10c1af40..49422319 100644 --- a/src/ExpandVolume/WinMain.cpp +++ b/src/ExpandVolume/WinMain.cpp @@ -490,6 +490,17 @@ BOOL CALLBACK ExtcvPasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARA SetWindowPos (hwndDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } + + if (!bSecureDesktopOngoing) + { + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; + } } return 0; @@ -782,6 +793,19 @@ BOOL CALLBACK ExtcvPasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARA DragFinish (hdrop); } return 1; + + case WM_NCDESTROY: + { + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; } return 0; diff --git a/src/Format/Tcformat.c b/src/Format/Tcformat.c index dd999f0c..ab7b165b 100644 --- a/src/Format/Tcformat.c +++ b/src/Format/Tcformat.c @@ -420,8 +420,8 @@ static void WipePasswordsAndKeyfiles (bool bFull) // Attempt to wipe passwords stored in the input field buffers wmemset (tmp, L'X', MAX_PASSWORD); tmp [MAX_PASSWORD] = 0; - SetWindowText (hPasswordInputField, tmp); - SetWindowText (hVerifyPasswordInputField, tmp); + SetWindowText (hPasswordInputField, tmp); + SetWindowText (hVerifyPasswordInputField, tmp); burn (&szVerify[0], sizeof (szVerify)); burn (&volumePassword, sizeof (volumePassword)); @@ -6372,6 +6372,14 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa strcpy (szRawPassword, "q"); #endif + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; + PostMessage (hwndDlg, TC_APPMSG_PERFORM_POST_WMINIT_TASKS, 0, 0); } return 0; @@ -8995,6 +9003,22 @@ ovf_end: case WM_CLOSE: PostMessage (hwndDlg, TC_APPMSG_FORMAT_USER_QUIT, 0, 0); return 1; + + case WM_NCDESTROY: + { + hPasswordInputField = NULL; + hVerifyPasswordInputField = NULL; + + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; } return 0; diff --git a/src/Mount/Mount.c b/src/Mount/Mount.c index 59ef7ba2..30714560 100644 --- a/src/Mount/Mount.c +++ b/src/Mount/Mount.c @@ -2415,6 +2415,17 @@ BOOL CALLBACK PasswordChangeDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPAR } CheckCapsLock (hwndDlg, FALSE); + + if (!bSecureDesktopOngoing) + { + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; + } return 0; } @@ -2880,6 +2891,19 @@ err: return 1; } return 0; + + case WM_NCDESTROY: + { + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; } return 0; @@ -3014,9 +3038,18 @@ BOOL CALLBACK PasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa SetFocus (GetDlgItem (hwndDlg, IDC_PASSWORD)); /* Start the timer to check if we are foreground only if Secure Desktop is not used */ + /* Implement Text drag-n-drop in order to support droping password from KeePass directly only if Secure Desktop is not used */ if (!bSecureDesktopOngoing) { SetTimer (hwndDlg, TIMER_ID_CHECK_FOREGROUND, TIMER_INTERVAL_CHECK_FOREGROUND, NULL); + + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; } } return 0; @@ -3281,6 +3314,19 @@ BOOL CALLBACK PasswordDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa } return 0; + case WM_NCDESTROY: + { + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; + case WM_CONTEXTMENU: { RECT buttonRect; @@ -3694,6 +3740,17 @@ BOOL CALLBACK MountOptionsDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM ToHyperlink (hwndDlg, IDC_LINK_HIDVOL_PROTECTION_INFO); + if (!bSecureDesktopOngoing) + { + PasswordEditDropTarget* pTarget = new PasswordEditDropTarget (); + if (pTarget->Register (hwndDlg)) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) pTarget); + } + else + delete pTarget; + } + } return 0; @@ -3851,6 +3908,19 @@ BOOL CALLBACK MountOptionsDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM } return 0; + + case WM_NCDESTROY: + { + /* unregister drap-n-drop support */ + PasswordEditDropTarget* pTarget = (PasswordEditDropTarget*) GetWindowLongPtr (hwndDlg, DWLP_USER); + if (pTarget) + { + SetWindowLongPtr (hwndDlg, DWLP_USER, (LONG_PTR) 0); + pTarget->Revoke (); + pTarget->Release(); + } + } + return 0; } return 0; -- cgit v1.2.3