如何使用 CredUIPromptForWindowsCredentials 验证凭据

How to validate credentials with CredUIPromptForWindowsCredentials

我不知道为什么我无法使用 CredUnPackAuthenticationBufferW 解压 CredUIPromptForWindowsCredentials 中使用的身份验证缓冲区,我总是得到 ERROR_INSUFFICIENT_BUFFER 错误。 我会感谢你的帮助。

std::wstring caption = L"Caption";
std::wstring msg= L"Msg";
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;

ULONG  authPackage = 0;
LPVOID outCredBuffer = nullptr;
ULONG  outCredSize = 0;
BOOL   save = false;

LPWSTR pszUserName = nullptr;
DWORD pcchlMaxUserName = 0;
LPWSTR pszDomainName = nullptr;
DWORD pcchMaxDomainName = 0;
LPWSTR pszPassword = nullptr;
DWORD pcchMaxPassword = 0;

DWORD result = CredUIPromptForWindowsCredentialsW(&credui,
                                                0,
                                                &authPackage,
                                                nullptr,
                                                0,
                                                &outCredBuffer,
                                                &outCredSize,
                                                &save,
                                                CREDUIWIN_ENUMERATE_ADMINS);


std::cout <<CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS
                                ,outCredBuffer
                                ,outCredSize
                                ,pszUserName
                                ,&pcchlMaxUserName
                                ,pszDomainName
                                ,&pcchMaxDomainName
                                ,pszPassword
                                ,&pcchMaxPassword) << std::endl;

std::cout << GetLastError() << std::endl; // out put 122 == ERROR_INSUFFICIENT_BUFFER 

这是典型的 winapi 模式 - api 必须 return 内存缓冲区中的一些信息。而是自己分配缓冲区——它要求调用者分配缓冲区。 所以调用者必须自己分配缓冲区并将它的指针和大小传递给 api。 api 检查缓冲区大小 - 如果它足够大以填充信息来缓冲,否则 return ERROR_INSUFFICIENT_BUFFER(假设没有其他错误)或有时 ERROR_MORE_DATA。具体错误返回 ERROR_INSUFFICIENT_BUFFERERROR_MORE_DATA 通常直接记录为 api 调用。这 2 个错误之间的不同之处在于:ERROR_INSUFFICIENT_BUFFER - 表示根本没有任何信息填充到缓冲区,而 ERROR_MORE_DATA 表示某些数据已 returned,但不完整。 和 api return 给用户,通过一些输出参数,在这种情况下需要缓冲区大小。通常这是通过相同的 inout 参数 - 指向 DWORD 的指针来完成的。在输入中指定用户分配缓冲区的大小,在输出中 - 指定所需的缓冲区大小或 returned 数据

的大小

经常需要哪个缓冲区大小 - 开始时未知。所以我们首先需要或调用 api 大小为 0 的缓冲区,或者分配一些据称足够的缓冲区大小。如果缓冲区不足 - 重新分配或扩展它并再次调用 api。对于某些 api(如 CredUnPackAuthenticationBufferW),所需的输出缓冲区不会随时间变化(如果输入参数未更改),但通常的输出缓冲区大小可能会在调用之间发生变化 - 即使第二次调用缓冲区大小 [=第一次调用 36=]ed 可能会因缓冲区大小错误而失败(因为 returned 数据可能会在两次调用之间增长)。在这种情况下需要在 do/while(error == ERROR_INSUFFICIENT_BUFFER/ERROR_MORE_DATA) 循环中调用 api。但即使输出缓冲区不随时间变化,我们也可以更好地做到这一点,这是在内部使用单个 api 调用的循环,而不是 2 个 api 调用。

具体的案例代码看起来像

ULONG cred()
{
    CREDUI_INFO ci = { sizeof(ci) };
    BOOL bSave = FALSE;

    PVOID pvOutAuthBuffer;
    ULONG ulOutAuthBufferSize;
    ULONG ulAuthPackage = 0;
    ULONG dwError = CredUIPromptForWindowsCredentials(
        &ci, NOERROR, &ulAuthPackage, 0, 0, 
        &pvOutAuthBuffer, &ulOutAuthBufferSize, 
        &bSave, CREDUIWIN_ENUMERATE_ADMINS );

    if (dwError == NOERROR)
    {
        ULONG cchUserName = 0;
        ULONG cchPassword = 0;
        ULONG cchDomain = 0;

        static volatile UCHAR guz = 0;

        PWSTR stack = (PWSTR)alloca(guz);
        PWSTR szUserName = 0, szPassword = 0, szDomainName = 0;

        ULONG cchNeed, cchAllocated = 0;

        do 
        {
            if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
            {
                szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
                cchAllocated = (ULONG)(stack - szUserName);
                szPassword = szUserName + cchUserName;
                szDomainName = szPassword + cchPassword;
            }

            dwError = CredUnPackAuthenticationBuffer(
                CRED_PACK_PROTECTED_CREDENTIALS, 
                pvOutAuthBuffer, ulOutAuthBufferSize, 
                szUserName, &cchUserName, 
                szDomainName, &cchDomain, 
                szPassword, &cchPassword)
                ? NOERROR : GetLastError();

            if (dwError == NOERROR)
            {
                DbgPrint("%S@%S %S\n", szDomainName, szUserName, szPassword);
                break;
            }

        } while (dwError == ERROR_INSUFFICIENT_BUFFER);

        CoTaskMemFree(pvOutAuthBuffer);
    }

    return dwError;
}

@RbMm - 你是对的!我使用 LogonUser 对其进行了测试,它运行良好。谢谢。 对于现成的解决方案,我得到了这个:

bool Authenticate_ADMIN_User(std::wstring caption, std::wstring msg, int maxReAsks = 0)
{
    CREDUI_INFOW credui   = {};
    credui.cbSize         = sizeof(credui);
    credui.hwndParent     = nullptr;
    credui.pszMessageText = msg.c_str();
    credui.pszCaptionText = caption.c_str();
    credui.hbmBanner      = nullptr;

    ULONG  authPackage   = 0,
           outCredSize   = 0;
    LPVOID outCredBuffer = nullptr;
    BOOL   save          = false;

    DWORD err   = 0;
    int   tries = 0;

    bool reAsk = false;

    do
    {
      tries++;

      if(CredUIPromptForWindowsCredentialsW(&credui,
                                         err,
                                         &authPackage,
                                         nullptr,
                                         0,
                                         &outCredBuffer,
                                         &outCredSize,
                                         &save,
                                         CREDUIWIN_ENUMERATE_ADMINS)

              != ERROR_SUCCESS)
          return false;


      ULONG cchUserName = 0;
      ULONG cchPassword = 0;
      ULONG cchDomain   = 0;
      ULONG cchNeed, cchAllocated = 0;

      static volatile UCHAR guz = 0;

      PWSTR stack = (PWSTR)alloca(guz);
      PWSTR szUserName = nullptr, szPassword = nullptr, szDomainName = nullptr;

      BOOL ret;

      do{
          if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
          {
              szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
              cchAllocated = (ULONG)(stack - szUserName);
              szPassword = szUserName + cchUserName;
              szDomainName = szPassword + cchPassword;
          }

          ret = CredUnPackAuthenticationBuffer(
              CRED_PACK_PROTECTED_CREDENTIALS , outCredBuffer, outCredSize, szUserName, &cchUserName,
              szDomainName, &cchDomain, szPassword,
              &cchPassword);

      }while(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);


        SecureZeroMemory(outCredBuffer, outCredSize);
        CoTaskMemFree(outCredBuffer);

        HANDLE handle = nullptr;

        if (LogonUser(szUserName, 
                      szDomainName,
                      szPassword,
                      LOGON32_LOGON_INTERACTIVE, 
                      LOGON32_PROVIDER_DEFAULT,
                      &handle)) 
        {

          CloseHandle(handle);
          return true;
        }

        else
        {
          err = ERROR_LOGON_FAILURE;
          reAsk = true;
        }


    }while(reAsk && tries < maxReAsks);

    return false;
}