0

I'm trying to display Windows default credential prompt and retrieve username, password and domain as strings.

I'm using this documentation:

CredUIPromptForWindowsCredentialsW

CredUnPackAuthenticationBufferW

When prompt displays, I enter random username and password (like, username: test, password: test123), hit enter and function CredUnPackAuthenticationBuffer() fails with ERROR_NOT_SUPPORTED

Code:

#include <Windows.h>
#include <wincred.h> //Link library Credui.lib for CredUIPromptForWindowsCredentials() to work
#include <iostream>
#include <string>

//Display error in console and close application
void DisplayConsoleError(const WCHAR* errorMessage, const WCHAR* fName);

int wmain(int argc, WCHAR* argv[])
{
        CREDUI_INFO cuiInfo;
    cuiInfo.cbSize = sizeof(CREDUI_INFO);
    cuiInfo.hbmBanner = nullptr;
    cuiInfo.hwndParent = nullptr;
    cuiInfo.pszCaptionText = L"CaptionText";
    cuiInfo.pszMessageText = L"MessageText";

    DWORD dwAuthError = 0;
    ULONG dwAuthPackage = 0;

    LPVOID outCredBuffer = nullptr;
    ULONG outCredBufferSize = 0;
    BOOL credSaveCheckbox = false;

    DWORD dwError = 0;
    DWORD lastError = 0;

    dwError = CredUIPromptForWindowsCredentials(
        &cuiInfo, 
        dwAuthError, 
        &dwAuthPackage, 
        nullptr, 
        NULL, 
        &outCredBuffer, 
        &outCredBufferSize, 
        &credSaveCheckbox, 
        CREDUIWIN_CHECKBOX | CREDUIWIN_GENERIC);

    if (dwError == ERROR_SUCCESS)
    {
        DWORD maxUserNameSize = CREDUI_MAX_USERNAME_LENGTH;
        DWORD maxDomainNameSize = CREDUI_MAX_DOMAIN_TARGET_LENGTH;
        DWORD maxPasswordLength = CREDUI_MAX_PASSWORD_LENGTH;

        LPWSTR szUserName = new WCHAR[maxUserNameSize];
        LPWSTR szDomain = new WCHAR[maxDomainNameSize];
        LPWSTR szPassword = new WCHAR[maxPasswordLength];

        DWORD dwCredBufferSize = outCredBufferSize;     //ULONG to DWORD

        DWORD lastError = 0;
        dwError = CredUnPackAuthenticationBuffer(
            CRED_PACK_GENERIC_CREDENTIALS,
            &outCredBuffer,
            dwCredBufferSize,
            szUserName,
            &maxUserNameSize,
            szDomain,
            &maxDomainNameSize,
            szPassword,
            &maxPasswordLength
        );
        lastError = GetLastError();

        //Check for error
        if (dwError == FALSE)
        {
            DisplayConsoleError(L"Blah", L"CredUnPackAuthenticationBuffer", lastError);
        }
        else
        {
            std::wcout << L"username " << szUserName << std::endl;
            std::wcout << L"domain " << szDomain << std::endl;
            std::wcout << L"password " << szPassword << std::endl;
        }

    }
    else
    {
        lastError = dwError;
    }

    SecureZeroMemory(outCredBuffer, outCredBufferSize);
    CoTaskMemFree(outCredBuffer);

    return lastError;
} 

Additionaly debugging CredUIPromptForWindowsCredentials() in VS2019 fails (problem with loading symbols?), but compiled .exe works fine. As a workaround im attaching debugger to process.

I'm beginner at winapi, so I would be grateful, if someone would explain why this error appears, what I'm doing wrong and how to fix this code.

EDIT Moved error check above SecureZeroMemory() and CoTaskMemFree() functions to avoid calling other API functions before checking error message, but error remained the same.

DisplayConsoleError:

void DisplayConsoleError(const WCHAR* errorMessage, const WCHAR* fName, DWORD lastError)
{
    std::cout << std::endl;
    std::cout << "Error\t" << std::endl;
    std::wcout << L"In function:\t" << fName << std::endl;
    std::cout << "Code:\t" << lastError << std::endl;
    std::cout << std::endl;
}

EDIT 2 Changes to code considering @RemyLebeau feedback

Zbychu
  • 13
  • 4
  • You are calling `CredUnPackAuthenticationBuffer()` even if `CredUIPromptForWindowsCredentials()` fails. And if `CredUnPackAuthenticationBuffer()` fails, you are calling other API functions before displaying the error, thus the error code could be wiped out before you retrieve it, so it is possible that the error 50 doesn't even belong to `CredUnPackAuthenticationBuffer()` at all. When using `GetLastError()` to retrieve error codes, ALWAYS call it IMMEDIATELY after the failed API that uses it. – Remy Lebeau Sep 15 '19 at 18:41
  • @RemyLebeau Thank you for response. I'm checking return value of ```CredUIPromptForWindowsCredentials()``` saved to variable ```dwError```. If it fails, function ```DisplayConsoleError()``` is called, which displays error message and closes the application. – Zbychu Sep 15 '19 at 19:10
  • I moved error check above ```SecureZeroMemory(outCredBuffer, outCredBufferSize); CoTaskMemFree(outCredBuffer); ``` but error number remains the same. – Zbychu Sep 15 '19 at 19:20
  • See [Why is using exit() considered bad?](https://stackoverflow.com/questions/25141737/) Also, your `DisplayConsoleError()` is still calling API functions (via `cout` and `wcout`) before calling `GetLastError()`. And FYI, `GetLastError()` is the wrong way to get an error code for a failed `CredUIPromptForWindowsCredentials()` anyway, so if `CredUIPromptForWindowsCredentials()` does fail, you won't report a proper error code for it. I would change `DisplayConsoleError()` to take an error code as an input parameter and let the caller decide how to obtain a proper error code to pass in. – Remy Lebeau Sep 16 '19 at 00:24
  • Thank you for exit() advice. I adjusted code to your feedback. Returned error code is still 50. – Zbychu Sep 17 '19 at 15:23
  • @JeffreyShao-MSFT can you explain? What do you mean by component? How do I check if it matches with my .exe? – Zbychu Sep 23 '19 at 09:02
  • Sorry,I misunderstand your meaning. Clean your project and rebuild it , do you have this issue ? – Jeffreys Sep 23 '19 at 09:36
  • @JeffreyShao-MSFT I did as you suggested, but error remains the same. – Zbychu Sep 23 '19 at 10:28

1 Answers1

1

Look at this function in your code:

CredUnPackAuthenticationBuffer(
            CRED_PACK_GENERIC_CREDENTIALS,
            &outCredBuffer,
            dwCredBufferSize,
            szUserName,
            &maxUserNameSize,
            szDomain,
            &maxDomainNameSize,
            szPassword,
            &maxPasswordLength
        );

You need to change &outCredBuffer to outCredBuffer.

#include <Windows.h>
#include <wincred.h> //Link library Credui.lib for CredUIPromptForWindowsCredentials() to work
#include <iostream>
#include <string>

//Display error in console and close application
void DisplayConsoleError(const WCHAR* errorMessage, const WCHAR* fName, DWORD lastError)
{
    std::cout << std::endl;
    std::cout << "Error\t" << std::endl;
    std::wcout << L"In function:\t" << fName << std::endl;
    std::cout << "Code:\t" << lastError << std::endl;
    std::cout << std::endl;
}

int wmain(int argc, WCHAR* argv[])
{
    CREDUI_INFO cuiInfo;
    cuiInfo.cbSize = sizeof(CREDUI_INFO);
    cuiInfo.hbmBanner = nullptr;
    cuiInfo.hwndParent = nullptr;
    cuiInfo.pszCaptionText = L"CaptionText";
    cuiInfo.pszMessageText = L"MessageText";

    DWORD dwAuthError = 0;
    ULONG dwAuthPackage = 0;

    LPVOID outCredBuffer = nullptr;
    ULONG outCredBufferSize = 0;
    BOOL credSaveCheckbox = false;

    DWORD dwError = 0;
    DWORD lastError = 0;

    dwError = CredUIPromptForWindowsCredentials(
        &cuiInfo,
        dwAuthError,
        &dwAuthPackage,
        nullptr,
        NULL,
        &outCredBuffer,
        &outCredBufferSize,
        &credSaveCheckbox,
        CREDUIWIN_CHECKBOX | CREDUIWIN_GENERIC);

    if (dwError == ERROR_SUCCESS)
    {
        DWORD maxUserNameSize = CREDUI_MAX_USERNAME_LENGTH;
        DWORD maxDomainNameSize = CREDUI_MAX_DOMAIN_TARGET_LENGTH;
        DWORD maxPasswordLength = CREDUI_MAX_PASSWORD_LENGTH;

        LPWSTR szUserName = new WCHAR[maxUserNameSize];
        LPWSTR szDomain = new WCHAR[maxDomainNameSize];
        LPWSTR szPassword = new WCHAR[maxPasswordLength];

        DWORD dwCredBufferSize = outCredBufferSize;     //ULONG to DWORD

        DWORD lastError = 0;
        dwError = CredUnPackAuthenticationBuffer(
            CRED_PACK_GENERIC_CREDENTIALS,
            outCredBuffer,
            dwCredBufferSize,
            szUserName,
            &maxUserNameSize,
            szDomain,
            &maxDomainNameSize,
            szPassword,
            &maxPasswordLength
        );
        lastError = GetLastError();

        //Check for error
        if (dwError == FALSE)
        {
            DisplayConsoleError(L"Blah", L"CredUnPackAuthenticationBuffer", lastError);
        }
        else
        {
            std::wcout << L"username " << szUserName << std::endl;
            std::wcout << L"domain " << szDomain << std::endl;
            std::wcout << L"password " << szPassword << std::endl;
        }

    }
    else
    {
        lastError = dwError;
    }

    SecureZeroMemory(outCredBuffer, outCredBufferSize);
    CoTaskMemFree(outCredBuffer);

    return lastError;
}
Jeffreys
  • 431
  • 2
  • 8