Windows: Support direct password drag-n-drop from external applications (e.g. KeePass) which is more secure than using clipboard.

This commit is contained in:
Mounir IDRASSI 2020-07-06 18:00:25 +02:00
parent b54729de48
commit ff391d9a6a
No known key found for this signature in database
GPG Key ID: 02C30AE90FAE4A6F
5 changed files with 597 additions and 2 deletions

View File

@ -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;
}
}
}
}

View File

@ -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<CLIPFORMAT> 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

View File

@ -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;

View File

@ -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;

View File

@ -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;