Files
fido2-manage.cpp/FIDO2.1 Manager.cpp
Token2 1b6ab4892c Update FIDO2.1 Manager.cpp
allow chars like @&*! (add escape function for PIN)
2025-03-03 17:48:56 +01:00

1157 lines
42 KiB
C++

#include <windows.h>
#include <commctrl.h> // For ListView and related controls
#include <tchar.h>
#include <string>
#include <vector>
#include <sstream>
#include <regex>
#include "resource.h"
#pragma comment(lib, "Comctl32.lib") // Link the Common Controls library
#define ID_COMBOBOX 101
#define ID_LISTVIEW 102
#define ID_REFRESH_BUTTON 103
// Global string variable
std::wstring globalPin = L"0000";
std::wstring deviceNumber = L"";
HINSTANCE hInst;
HWND hComboBox, hListView, hRefreshButton, hMainWindow, hwnd;
struct PasskeyInfo {
std::wstring credentialId;
std::wstring user;
std::wstring domain;
};
std::wstring EscapeCommandLineArgument(const std::wstring& input) {
std::wstring escaped = L"\""; // Start with a quote to properly handle spaces
for (wchar_t ch : input) {
switch (ch) {
case L'"': escaped += L"\\\""; break; // Escape double quotes
case L'^':
case L'&':
case L'|':
case L'<':
case L'>':
case L'%':
case L'!':
case L'(':
case L')':
case L'=':
case L';':
case L'`':
case L',':
case L'[':
case L']':
case L'{':
case L'}':
case L'*':
case L'?':
case L'\\': // Escape special characters for CMD
escaped += L'^';
escaped += ch;
break;
default:
escaped += ch;
}
}
escaped += L"\""; // End with a quote
return escaped;
}
// Function to execute a command and capture its output
std::vector<std::wstring> RunCommandAndGetOutput(const std::wstring& command) {
std::vector<std::wstring> lines;
HANDLE hPipeRead, hPipeWrite;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
if (!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) {
return lines;
}
STARTUPINFO si = { sizeof(STARTUPINFO) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = { 0 };
if (CreateProcess(
NULL,
const_cast<LPWSTR>(command.c_str()),
NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
CloseHandle(hPipeWrite);
char buffer[1024];
DWORD bytesRead;
while (ReadFile(hPipeRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = '\0';
int wideSize = MultiByteToWideChar(CP_OEMCP, 0, buffer, -1, NULL, 0);
if (wideSize > 0) {
std::wstring wideBuffer(wideSize, L'\0');
MultiByteToWideChar(CP_OEMCP, 0, buffer, -1, &wideBuffer[0], wideSize);
std::wstringstream ss(wideBuffer);
std::wstring line;
while (std::getline(ss, line)) {
lines.push_back(line);
}
}
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
CloseHandle(hPipeRead);
return lines;
}
std::vector<PasskeyInfo> ParseResidentKeys(const std::vector<std::wstring>& output) {
std::vector<PasskeyInfo> passkeys;
for (const auto& line : output) {
size_t credPos = line.find(L"Credential ID:");
size_t userPos = line.find(L"User:");
if (credPos != std::wstring::npos && userPos != std::wstring::npos) {
PasskeyInfo info;
info.credentialId = line.substr(credPos + 14, userPos - (credPos + 14));
info.credentialId = info.credentialId.substr(info.credentialId.find_first_not_of(L" "));
info.user = line.substr(userPos + 5);
info.user = info.user.substr(info.user.find_first_not_of(L" "));
passkeys.push_back(info);
}
}
return passkeys;
}
std::vector<std::wstring> GetDomains(const std::wstring& deviceNumber, const std::wstring& globalPin) {
// Command for the first run
std::wstring command = L".\\fido2-manage.exe -residentkeys -device " + deviceNumber + L" -pin " + globalPin;
// Debug: Show the command being executed
//MessageBox(NULL, command.c_str(), L"Debug: Command Being Executed", MB_OK);
std::vector<std::wstring> output = RunCommandAndGetOutput(command);
// Debug: Check the raw output
for (const auto& line : output) {
// MessageBox(NULL, line.c_str(), L"Debug: First Run Output", MB_OK);
}
// Extract domains (Users) from the output
std::vector<std::wstring> domains;
for (const auto& line : output) {
size_t userPos = line.find(L"User:");
if (userPos != std::wstring::npos) {
std::wstring domain = line.substr(userPos + 5);
domain = domain.substr(domain.find_first_not_of(L" "));
domains.push_back(domain);
}
}
return domains;
}
std::vector<PasskeyInfo> GetResidentKeysWithDomains(const std::wstring& deviceNumber, const std::wstring& globalPin, const std::vector<std::wstring>& domains) {
std::vector<PasskeyInfo> passkeys;
for (const auto& domain : domains) {
// Command for the second run
std::wstring command = L".\\fido2-manage.exe -residentkeys -device " + deviceNumber +
L" -domain " + domain + L" -pin " + globalPin;
std::vector<std::wstring> output = RunCommandAndGetOutput(command);
// Debug: Check the raw output
for (const auto& line : output) {
//MessageBox(NULL, line.c_str(), L"Debug: Second Run Output", MB_OK);
}
// Parse the results and add them to the list
std::vector<PasskeyInfo> parsedPasskeys = ParseResidentKeys(output);
for (auto& key : parsedPasskeys) {
key.domain = domain; // Assuming PasskeyInfo has a domain member.
}
passkeys.insert(passkeys.end(), parsedPasskeys.begin(), parsedPasskeys.end());
}
return passkeys;
}
void DisableAllButtons(HWND hwnd) {
// Disable Passkeys button
HWND hPasskeysButton = GetDlgItem(hwnd, ID_BTN_PASSKEYS);
if (hPasskeysButton) {
EnableWindow(hPasskeysButton, FALSE);
RedrawWindow(hPasskeysButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Disable Fingerprints button
HWND hFingerprintButton = GetDlgItem(hwnd, ID_BTN_FINGERPRINT);
if (hFingerprintButton) {
EnableWindow(hFingerprintButton, FALSE);
RedrawWindow(hFingerprintButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Disable Change PIN button
HWND hChangePinButton = GetDlgItem(hwnd, ID_BTN_CHANGEPIN);
if (hChangePinButton) {
EnableWindow(hChangePinButton, FALSE);
RedrawWindow(hChangePinButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Disable Enforce UV button
HWND hEnforceUVButton = GetDlgItem(hwnd, ID_BTN_ENFORCEUV);
if (hEnforceUVButton) {
EnableWindow(hEnforceUVButton, FALSE);
RedrawWindow(hEnforceUVButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Disable Reset button
HWND hResetButton = GetDlgItem(hwnd, ID_BTN_RESET);
if (hResetButton) {
EnableWindow(hResetButton, FALSE);
RedrawWindow(hResetButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
}
void PopulatePasskeysList(HWND hListView, const std::vector<PasskeyInfo>& passkeys) {
for (size_t i = 0; i < passkeys.size(); ++i) {
const PasskeyInfo& pk = passkeys[i];
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.iItem = static_cast<int>(i);
// Add Credential ID (Column 0)
lvItem.pszText = const_cast<LPWSTR>(pk.credentialId.c_str());
SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
// Add User (Column 1)
lvItem.iSubItem = 1;
lvItem.pszText = const_cast<LPWSTR>(pk.user.c_str());
SendMessage(hListView, LVM_SETITEM, 0, (LPARAM)&lvItem);
// Add Domain (Column 2)
lvItem.iSubItem = 2;
lvItem.pszText = const_cast<LPWSTR>(pk.domain.c_str());
SendMessage(hListView, LVM_SETITEM, 0, (LPARAM)&lvItem);
}
}
INT_PTR CALLBACK PasskeysDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
static HWND hListView;
static std::vector<PasskeyInfo>* passkeys = nullptr;
switch (message) {
case WM_INITDIALOG: {
hListView = GetDlgItem(hDlg, IDC_LISTVIEW);
// Initialize ListView columns
LVCOLUMN lvColumn = { 0 };
lvColumn.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvColumn.cx = 100;
lvColumn.pszText = const_cast<LPWSTR>(L"Credential ID");
SendMessage(hListView, LVM_INSERTCOLUMN, 0, (LPARAM)&lvColumn);
lvColumn.cx = 200;
lvColumn.pszText = const_cast<LPWSTR>(L"User");
SendMessage(hListView, LVM_INSERTCOLUMN, 1, (LPARAM)&lvColumn);
lvColumn.cx = 200; // Set the width for the Domain column
lvColumn.pszText = const_cast<LPWSTR>(L"Domain");
SendMessage(hListView, LVM_INSERTCOLUMN, 2, (LPARAM)&lvColumn);
// Store the initial passkeys pointer
passkeys = reinterpret_cast<std::vector<PasskeyInfo>*>(lParam);
// Populate the ListView with initial data
PopulatePasskeysList(hListView, *passkeys);
return TRUE;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_BTN_REFRESH: {
// Set the wait cursor
HCURSOR hWaitCursor = LoadCursor(NULL, IDC_WAIT);
SetCursor(hWaitCursor);
// Process pending messages to update the cursor
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Clear the ListView
SendMessage(hListView, LVM_DELETEALLITEMS, 0, 0);
// Step 1: First Run - Fetch domains
std::wstring firstCommand = L".\\fido2-manage.exe -residentkeys -device " + deviceNumber + L" -pin " + globalPin;
std::vector<std::wstring> firstOutput = RunCommandAndGetOutput(firstCommand);
// Parse the output to get domains (users)
std::vector<std::wstring> domains;
for (const auto& line : firstOutput) {
size_t userPos = line.find(L"User:");
if (userPos != std::wstring::npos) {
std::wstring domain = line.substr(userPos + 5);
domain = domain.substr(domain.find_first_not_of(L" ")); // Trim leading spaces
domains.push_back(domain);
}
}
// Debug: Display the parsed domains
for (const auto& domain : domains) {
// MessageBox(hDlg, domain.c_str(), L"Debug: Parsed Domain", MB_OK);
}
// Step 2: Second Run - Fetch resident keys for each domain
std::vector<PasskeyInfo> refreshedPasskeys;
for (const auto& domain : domains) {
std::wstring secondCommand = L".\\fido2-manage.exe -residentkeys -device " + deviceNumber +
L" -domain " + domain + L" -pin " + globalPin;
std::vector<std::wstring> secondOutput = RunCommandAndGetOutput(secondCommand);
// Parse the second run output
std::vector<PasskeyInfo> parsedPasskeys = ParseResidentKeys(secondOutput);
for (auto& key : parsedPasskeys) {
key.domain = domain; // Assuming PasskeyInfo has a domain member.
}
refreshedPasskeys.insert(refreshedPasskeys.end(), parsedPasskeys.begin(), parsedPasskeys.end());
}
// Debug: Display parsed passkeys
for (const auto& pk : refreshedPasskeys) {
// MessageBox(hDlg, (L"Credential ID: " + pk.credentialId + L"\nUser: " + pk.user).c_str(), L"Debug: Parsed Passkey", MB_OK);
}
// Step 3: Populate the ListView with the complete data
PopulatePasskeysList(hListView, refreshedPasskeys);
// Update the global passkeys pointer
*passkeys = refreshedPasskeys;
// Restore the default cursor
HCURSOR hArrowCursor = LoadCursor(NULL, IDC_ARROW);
SetCursor(hArrowCursor);
return TRUE;
}
case IDC_BTN_SHOW_SELECTED: {
// Existing logic to handle the selected row
int selectedRow = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
if (selectedRow == -1) {
MessageBox(hDlg, L"No row selected.", L"Error", MB_OK | MB_ICONERROR);
return TRUE;
}
wchar_t buffer[256] = { 0 };
LVITEM lvItem = { 0 };
lvItem.iItem = selectedRow;
lvItem.iSubItem = 0; // First column
lvItem.pszText = buffer;
lvItem.cchTextMax = 256;
if (SendMessage(hListView, LVM_GETITEMTEXT, selectedRow, (LPARAM)&lvItem) > 0) {
std::wstring command = L".\\fido2-manage.exe -delete -device " + deviceNumber + L" -credential " + buffer;
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k " + command).c_str(), NULL, SW_SHOW);
}
else {
MessageBox(hDlg, L"Failed to retrieve row content.", L"Error", MB_OK | MB_ICONERROR);
}
return TRUE;
}
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
std::wstring ShowInputBox(HWND hWnd, const std::wstring& title, const std::wstring& prompt) {
static wchar_t buffer[256] = { 0 };
// Show the input dialog
DialogBoxParam(
GetModuleHandle(NULL),
MAKEINTRESOURCE(101), // ID of the dialog resource
hWnd,
[](HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -> INT_PTR {
switch (message) {
case WM_INITDIALOG: {
// Set the prompt text
SetDlgItemText(hDlg, 1001, reinterpret_cast<LPCWSTR>(lParam));
// Center the dialog on the screen
RECT rcDlg, rcScreen;
GetWindowRect(hDlg, &rcDlg);
SystemParametersInfo(SPI_GETWORKAREA, 0, &rcScreen, 0); // Get the working area of the screen
int x = (rcScreen.right - rcScreen.left - (rcDlg.right - rcDlg.left)) / 2;
int y = (rcScreen.bottom - rcScreen.top - (rcDlg.bottom - rcDlg.top)) / 2;
SetWindowPos(hDlg, HWND_TOP, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
return TRUE;
}
case WM_COMMAND:
if (LOWORD(wParam) == IDOK) {
GetDlgItemText(hDlg, 1002, buffer, 256); // Get input from the edit box
EndDialog(hDlg, IDOK);
return TRUE;
}
if (LOWORD(wParam) == IDCANCEL) {
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
break;
}
return FALSE;
},
reinterpret_cast<LPARAM>(prompt.c_str()));
return buffer; // Return the user input
}
INT_PTR CALLBACK FingerprintDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
static HWND hListView;
switch (message) {
case WM_INITDIALOG: {
hListView = GetDlgItem(hDlg, IDC_LISTVIEW_FINGERPRINTS);
// Initialize ListView columns
LVCOLUMN lvColumn = { 0 };
lvColumn.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvColumn.cx = 100;
lvColumn.pszText = const_cast<LPWSTR>(L"Fingerprint ID");
SendMessage(hListView, LVM_INSERTCOLUMN, 0, (LPARAM)&lvColumn);
lvColumn.cx = 300;
lvColumn.pszText = const_cast<LPWSTR>(L"Description");
SendMessage(hListView, LVM_INSERTCOLUMN, 1, (LPARAM)&lvColumn);
// Populate the ListView with fingerprints
std::wstring command = L".\\fido2-manage.exe -fingerprintlist -device " + deviceNumber + L" -pin " + globalPin;
std::vector<std::wstring> output = RunCommandAndGetOutput(command);
for (const auto& line : output) {
// Split the line into parts
size_t colonPos = line.find(L":");
size_t spacePos = line.find(L" ", colonPos + 2);
if (colonPos != std::wstring::npos && spacePos != std::wstring::npos) {
// Extract Index (first part)
std::wstring index = line.substr(0, colonPos);
index = index.substr(index.find_first_not_of(L" ")); // Trim spaces
// Extract Fingerprint ID and Description (combined into second column)
std::wstring fingerprintID_And_Description = line.substr(colonPos + 2); // From after ':'
// Debug: Display the parsed data
// MessageBox(hDlg, (L"Index: " + index + L"\nData: " + fingerprintID_And_Description).c_str(), L"Debug: Parsed Data", MB_OK);
// Add to the ListView
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.iItem = static_cast<int>(SendMessage(hListView, LVM_GETITEMCOUNT, 0, 0));
// Add Index to the first column
wchar_t indexBuffer[256];
wcscpy_s(indexBuffer, index.c_str());
lvItem.pszText = indexBuffer;
SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
// Add Fingerprint ID and Description to the second column
lvItem.iSubItem = 1;
wchar_t dataBuffer[256];
wcscpy_s(dataBuffer, fingerprintID_And_Description.c_str());
lvItem.pszText = dataBuffer;
SendMessage(hListView, LVM_SETITEM, 0, (LPARAM)&lvItem);
}
else {
// Debug: Show lines that failed to parse
// MessageBox(hDlg, line.c_str(), L"Debug: Unparsed Line", MB_OK);
}
}
return TRUE;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_BTN_ADD_FINGERPRINT: {
// Implement Add Fingerprint functionality
// Construct the add fingerprint command
std::wstring addCommand = L".\\fido2-manage.exe -fingerprint -device " + deviceNumber;
// Execute the command in a new console window
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k " + addCommand).c_str(), NULL, SW_SHOW);
return TRUE;
}
case IDC_BTN_DELETE_FINGERPRINT: {
// Get the selected row
int selectedRow = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
if (selectedRow == -1) {
MessageBox(hDlg, L"No fingerprint selected.", L"Error", MB_OK | MB_ICONERROR);
return TRUE;
}
// Get the content of the first column (Fingerprint ID)
wchar_t buffer[256] = { 0 };
LVITEM lvItem = { 0 };
lvItem.iItem = selectedRow;
lvItem.iSubItem = 0; // First column
lvItem.pszText = buffer;
lvItem.cchTextMax = 256;
if (SendMessage(hListView, LVM_GETITEMTEXT, selectedRow, (LPARAM)&lvItem) > 0) {
// Build the delete command
std::wstring deleteCommand = L".\\fido2-manage.exe -deletefingerprint " + std::wstring(buffer) + L" -device " + std::wstring(deviceNumber);
// Execute the delete command
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k " + deleteCommand).c_str(), NULL, SW_SHOW);
}
else {
MessageBox(hDlg, L"Failed to retrieve fingerprint ID.", L"Error", MB_OK | MB_ICONERROR);
}
return TRUE;
}
}
break;
case WM_CLOSE:
EndDialog(hDlg, 0);
return TRUE;
}
return FALSE;
}
// Function declarations
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
std::vector<std::wstring> RunCommandAndGetOutput(const std::wstring& command);
void PopulateComboBox();
void PopulateListView(const std::wstring& deviceNumber);
void ResizeControls(HWND hwnd);
void RefreshData();
// Entry point
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
hInst = hInstance;
// Register the window class
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = _T("DeviceInfoApp");
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
WS_EX_APPWINDOW, // Extended window styles
_T("DeviceInfoApp"), // Window class name
_T("FIDO2.1 Manager"), // Window title
WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, // Window styles
CW_USEDEFAULT, CW_USEDEFAULT, // Position
600, 430, // Size
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (!hwnd) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
#include <thread>
// Function to execute a command with a timeout
std::vector<std::wstring> RunCommandAndGetOutputWithTimeout(const std::wstring& command, DWORD timeoutMs) {
std::vector<std::wstring> lines;
HANDLE hPipeRead, hPipeWrite;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
if (!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) {
return lines;
}
STARTUPINFO si = { sizeof(STARTUPINFO) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = { 0 };
if (CreateProcess(
NULL,
const_cast<LPWSTR>(command.c_str()),
NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
CloseHandle(hPipeWrite);
// Wait for the process to finish or timeout
DWORD waitResult = WaitForSingleObject(pi.hProcess, timeoutMs);
if (waitResult == WAIT_TIMEOUT) {
// Timeout: Kill the process
TerminateProcess(pi.hProcess, 1);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hPipeRead);
return lines; // Return empty result on timeout
}
// Process completed within timeout, read output
char buffer[1024];
DWORD bytesRead;
while (ReadFile(hPipeRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = '\0';
int wideSize = MultiByteToWideChar(CP_OEMCP, 0, buffer, -1, NULL, 0);
if (wideSize > 0) {
std::wstring wideBuffer(wideSize, L'\0');
MultiByteToWideChar(CP_OEMCP, 0, buffer, -1, &wideBuffer[0], wideSize);
std::wstringstream ss(wideBuffer);
std::wstring line;
while (std::getline(ss, line)) {
lines.push_back(line);
}
}
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
CloseHandle(hPipeRead);
return lines;
}
// Populate the ComboBox with output from `fido2-manage.exe -list`
void PopulateComboBox() {
std::vector<std::wstring> devices = RunCommandAndGetOutput(L".\\fido2-manage.exe -list");
for (const auto& device : devices) {
SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)device.c_str());
}
}
// Populate the ListView with output from `fido2-manage.exe -info`
void PopulateListView(HWND hwnd, const std::wstring& deviceNumber) {
// Clear existing items
SendMessage(hListView, LVM_DELETEALLITEMS, 0, 0);
DisableAllButtons(hwnd);
// Show the input box and store the result in globalPIN
globalPin = ShowInputBox(NULL, L"Enter PIN", L"Please enter your PIN:");
globalPin = EscapeCommandLineArgument(globalPin);
const size_t MIN_PIN_LENGTH = 4;
if (globalPin.length() < MIN_PIN_LENGTH) {
// Display an error message
MessageBoxW(NULL, L"Error: PIN must be at least 4 characters long.", L"Invalid PIN", MB_OK | MB_ICONERROR);
// Terminate the application gracefully
RefreshData();
return;
}
// Construct the command
std::wstring command = L".\\fido2-manage.exe -storage -device " + deviceNumber + L" -pin " + globalPin;
// Execute the command
std::vector<std::wstring> output = RunCommandAndGetOutput(command);
// Check if the output contains "FIDO_ERR_PIN_REQUIRED"
for (const auto& line : output) {
if (line.find(L"FIDO_ERR_PIN_REQUIRED") != std::wstring::npos) {
MessageBox(NULL, _T("This device does not have a PIN set. Please set a PIN before proceeding."), _T("PIN Required"), MB_ICONWARNING);
std::wstring setPinCommand = L".\\fido2-manage.exe -setPIN -device " + deviceNumber;
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k " + setPinCommand).c_str(), NULL, SW_SHOW);
RefreshData();
return; // Exit the function early if PIN is required
}
if (line.find(L"FIDO_ERR_PIN_INVALID") != std::wstring::npos) {
MessageBox(NULL, _T("Wrong PIN provided."), _T("PIN Required"), MB_ICONWARNING);
RefreshData();
return; // Exit the function early if PIN is required
}
}
// Construct the second command
std::wstring command2 = L".\\fido2-manage.exe -info -device " + deviceNumber;
// Execute the second command
std::vector<std::wstring> additionalOutput = RunCommandAndGetOutput(command2);
// Append the second command's output to the existing output vector
output.insert(output.end(), additionalOutput.begin(), additionalOutput.end());
// Enable Change PIN button unconditionally
HWND hChangePinButton = GetDlgItem(hwnd, ID_BTN_CHANGEPIN);
if (hChangePinButton) {
EnableWindow(hChangePinButton, TRUE);
RedrawWindow(hChangePinButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Enable Reset button unconditionally
HWND hResetButton = GetDlgItem(hwnd, ID_BTN_RESET);
if (hResetButton) {
EnableWindow(hResetButton, TRUE);
RedrawWindow(hResetButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Add items to the ListView
for (size_t i = 0; i < output.size(); ++i) {
std::wstring line = output[i];
size_t pos = line.find(L':');
std::wstring name = (pos != std::wstring::npos) ? line.substr(0, pos) : line;
std::wstring value = (pos != std::wstring::npos) ? line.substr(pos + 1) : L"";
// Replace text in names
if (name.find(L"existing rk(s)") != std::wstring::npos) {
name = L"existing passkeys";
}
if (name.find(L"remaining rk(s)") != std::wstring::npos) {
name = L"remaining passkeys";
}
if (name == L"existing passkeys") {
// MessageBox(hwnd, (L"Name: " + name + L", Value: " + value).c_str(), L"Debug: Existing Passkeys", MB_OK);
try {
int count = std::stoi(value);
// MessageBox(hwnd, (L"Count: " + std::to_wstring(count)).c_str(), L"Debug: Conversion Success", MB_OK);
if (count > 0) {
// MessageBox(hwnd, L"Enabling Passkeys Button", L"Debug: Button Enabled", MB_OK);
HWND hButton = GetDlgItem(hwnd, ID_BTN_PASSKEYS);
if (hButton) {
EnableWindow(hButton, TRUE); // Enable the button
// Force a redraw to ensure the UI updates
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
else {
// MessageBox(hwnd, L"Button handle is null!", L"Debug: Button Error", MB_OK);
}
}
}
catch (...) {
// MessageBox(hwnd, L"Exception during conversion!", L"Debug: Conversion Failed", MB_OK);
}
}
if (name == L"version strings") {
// Debug: Show the version strings value
// MessageBox(hwnd, (L"Version Strings: " + value).c_str(), L"Debug: Version Strings", MB_OK);
// Search for "FIDO_2_1"
size_t posFIDO_2_1 = value.find(L"FIDO_2_1");
// Check if "FIDO_2_1" exists and is not followed by "_"
if (posFIDO_2_1 != std::wstring::npos &&
(posFIDO_2_1 + 8 >= value.length() || value[posFIDO_2_1 + 8] != L'_')) {
// MessageBox(hwnd, L"Enabling Enforce UV Button", L"Debug: Button Enabled", MB_OK);
HWND hButton = GetDlgItem(hwnd, ID_BTN_ENFORCEUV);
if (hButton) {
EnableWindow(hButton, TRUE); // Enable the button
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
else {
// MessageBox(hwnd, L"Enforce UV Button handle is null!", L"Debug: Button Error", MB_OK);
}
}
else {
// MessageBox(hwnd, L"Conditions not met: Button will not be enabled", L"Debug: Button Not Enabled", MB_OK);
}
}
if (name == L"options") {
// Debug: Show the options value
// MessageBox(hwnd, (L"Options: " + value).c_str(), L"Debug: Options", MB_OK);
// Check if "bioEnroll" exists in the options
if (value.find(L"bioEnroll") != std::wstring::npos) {
// MessageBox(hwnd, L"Enabling Fingerprints Button", L"Debug: Button Enabled", MB_OK);
HWND hButton = GetDlgItem(hwnd, ID_BTN_FINGERPRINT);
if (hButton) {
EnableWindow(hButton, TRUE); // Enable the Fingerprints button
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
else {
// MessageBox(hwnd, L"Fingerprints Button handle is null!", L"Debug: Button Error", MB_OK);
}
}
else {
// MessageBox(hwnd, L"bioEnroll not found in options", L"Debug: Button Not Enabled", MB_OK);
}
}
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.iItem = static_cast<int>(i);
// Add Name
wchar_t nameBuffer[256];
wcscpy_s(nameBuffer, name.c_str());
lvItem.pszText = nameBuffer;
SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
// Add Value
wchar_t valueBuffer[256];
wcscpy_s(valueBuffer, value.c_str());
lvItem.iSubItem = 1;
lvItem.pszText = valueBuffer;
SendMessage(hListView, LVM_SETITEM, 0, (LPARAM)&lvItem);
}
}
// Resize controls when the window is resized
void ResizeControls(HWND hwnd) {
RECT rect;
GetClientRect(hwnd, &rect);
// Resize ComboBox and Refresh Button
int buttonWidth = 100;
SetWindowPos(hComboBox, NULL, 10, 10, rect.right - buttonWidth - 30, 200, SWP_NOZORDER);
SetWindowPos(hRefreshButton, NULL, rect.right - buttonWidth - 10, 10, buttonWidth, 25, SWP_NOZORDER);
// Resize ListView
SetWindowPos(hListView, NULL, 10, 50, rect.right - 20, rect.bottom - 100, SWP_NOZORDER);
// Calculate horizontal spacing for buttons
int buttonHeight = 30;
int buttonSpacing = 10; // Spacing between buttons
int totalButtonWidth = 5 * (100 + buttonSpacing) - buttonSpacing; // 5 buttons of width 100
int startX = (rect.right - totalButtonWidth) / 2; // Center align buttons
// Adjust positions for new buttons
SetWindowPos(GetDlgItem(hwnd, ID_BTN_PASSKEYS), NULL, startX, rect.bottom - buttonHeight - 10, 100, buttonHeight, SWP_NOZORDER);
SetWindowPos(GetDlgItem(hwnd, ID_BTN_FINGERPRINT), NULL, startX + 110, rect.bottom - buttonHeight - 10, 100, buttonHeight, SWP_NOZORDER);
SetWindowPos(GetDlgItem(hwnd, ID_BTN_CHANGEPIN), NULL, startX + 220, rect.bottom - buttonHeight - 10, 100, buttonHeight, SWP_NOZORDER);
SetWindowPos(GetDlgItem(hwnd, ID_BTN_ENFORCEUV), NULL, startX + 330, rect.bottom - buttonHeight - 10, 100, buttonHeight, SWP_NOZORDER);
SetWindowPos(GetDlgItem(hwnd, ID_BTN_RESET), NULL, startX + 440, rect.bottom - buttonHeight - 10, 100, buttonHeight, SWP_NOZORDER);
// Force redraw of the entire client area to prevent garbled visuals
RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
// Clear and repopulate data
void RefreshData() {
DisableAllButtons(hwnd);
// Clear ComboBox
SendMessage(hComboBox, CB_RESETCONTENT, 0, 0);
// Clear ListView
SendMessage(hListView, LVM_DELETEALLITEMS, 0, 0);
// Repopulate ComboBox
PopulateComboBox();
}
// Window procedure
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_GETMINMAXINFO : {
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
// Set minimum window size
mmi->ptMinTrackSize.x = 600; // Minimum width
mmi->ptMinTrackSize.y = 430; // Minimum height
break;
}
case WM_CREATE: {
// Initialize common controls
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
// Create ComboBox
hComboBox = CreateWindow(
_T("COMBOBOX"),
NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | CBS_HASSTRINGS | WS_VSCROLL,
10, 10, 200, 200,
hwnd,
(HMENU)ID_COMBOBOX,
hInst,
NULL);
// Create Refresh Button
hRefreshButton = CreateWindow(
_T("BUTTON"),
_T("Refresh"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
220, 10, 100, 25,
hwnd,
(HMENU)ID_REFRESH_BUTTON,
hInst,
NULL);
// Create ListView
hListView = CreateWindow(
WC_LISTVIEW,
NULL,
WS_CHILD | WS_VISIBLE | LVS_REPORT,
10, 50, 460, 300,
hwnd,
(HMENU)ID_LISTVIEW,
hInst,
NULL);
// Add columns to the ListView
LVCOLUMN lvColumn = { 0 };
lvColumn.mask = LVCF_TEXT | LVCF_WIDTH;
// Add "Name" column
lvColumn.cx = 200;
wchar_t columnName[] = L"Name";
lvColumn.pszText = columnName;
SendMessage(hListView, LVM_INSERTCOLUMN, 0, (LPARAM)&lvColumn);
// Add "Value" column
lvColumn.cx = 260;
wchar_t columnValue[] = L"Value";
lvColumn.pszText = columnValue;
SendMessage(hListView, LVM_INSERTCOLUMN, 1, (LPARAM)&lvColumn);
// Create buttons at the bottom (initially disabled)
HWND hPasskeysButton = CreateWindow(
L"BUTTON", L"Passkeys",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_DISABLED | BS_DEFPUSHBUTTON,
10, 360, 100, 30, // Position and size (x, y, width, height)
hwnd, (HMENU)ID_BTN_PASSKEYS, hInst, NULL);
HWND hFingerprintButton = CreateWindow(
L"BUTTON", L"Fingerprints",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_DISABLED | BS_DEFPUSHBUTTON,
120, 360, 100, 30,
hwnd, (HMENU)ID_BTN_FINGERPRINT, hInst, NULL);
HWND hChangePinButton = CreateWindow(
L"BUTTON", L"Change PIN",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_DISABLED | BS_DEFPUSHBUTTON,
230, 360, 100, 30,
hwnd, (HMENU)ID_BTN_CHANGEPIN, hInst, NULL);
HWND hEnforceUVButton = CreateWindow(
L"BUTTON", L"Enforce UV",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_DISABLED | BS_DEFPUSHBUTTON,
340, 360, 100, 30,
hwnd, (HMENU)ID_BTN_ENFORCEUV, hInst, NULL);
HWND hResetButton = CreateWindow(
L"BUTTON", L"Reset",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_DISABLED | BS_DEFPUSHBUTTON,
450, 360, 100, 30,
hwnd, (HMENU)ID_BTN_RESET, hInst, NULL);
// Populate the ComboBox
PopulateComboBox();
break;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
// Example: Enable the "Passkeys" button when another action is taken
case ID_BTN_PASSKEYS: {
// Set the wait cursor
HCURSOR hWaitCursor = LoadCursor(NULL, IDC_WAIT);
SetCursor(hWaitCursor);
// Process pending messages to update the cursor
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Step 1: Get domains from the first run
std::vector<std::wstring> domains = GetDomains(deviceNumber, globalPin);
// Debug: Check the extracted domains
for (const auto& domain : domains) {
//MessageBox(hwnd, domain.c_str(), L"Debug: Extracted Domain", MB_OK);
}
// Step 2: Get resident keys with domains
std::vector<PasskeyInfo> passkeys = GetResidentKeysWithDomains(deviceNumber, globalPin, domains);
// Debug: Check the parsed results from the second run
for (const auto& pk : passkeys) {
// MessageBox(hwnd, (L"Credential ID: " + pk.credentialId + L"\nUser: " + pk.user).c_str(), L"Debug: Parsed Passkey", MB_OK);
}
// Restore the default cursor
HCURSOR hArrowCursor = LoadCursor(NULL, IDC_ARROW);
SetCursor(hArrowCursor);
// Step 3: Pass the results to the dialog
DialogBoxParam(
hInst,
MAKEINTRESOURCE(IDD_PASSKEYS_DIALOG),
hwnd,
PasskeysDialogProc,
reinterpret_cast<LPARAM>(&passkeys)
);
break;
}
case ID_BTN_FINGERPRINT:
DialogBox(
hInst,
MAKEINTRESOURCE(IDD_FINGERPRINT_DIALOG),
hwnd,
FingerprintDialogProc
);
break;
case ID_BTN_CHANGEPIN:
{
// Construct the change PIN command
std::wstring chPINCommand = L".\\fido2-manage.exe -changepin -device " + deviceNumber;
// Execute the command in a new console window
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k \"" + chPINCommand + L"\"").c_str(), NULL, SW_SHOW);
break;
}
case ID_BTN_ENFORCEUV:
{
// Construct the enforce UV command
std::wstring UVSCommand = L".\\fido2-manage.exe -uvs -device " + deviceNumber;
// Execute the command in a new console window
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k \"" + UVSCommand + L"\"").c_str(), NULL, SW_SHOW);
break;
}
case ID_BTN_RESET:
{
// Construct the reset command
std::wstring resetCommand = L".\\fido2-manage.exe -reset -device " + deviceNumber;
// Execute the command in a new console window
ShellExecute(NULL, L"open", L"cmd.exe", (L"/k \"" + resetCommand + L"\"").c_str(), NULL, SW_SHOW);
break;
}
}
if (LOWORD(wParam) == ID_COMBOBOX && HIWORD(wParam) == CBN_SELCHANGE) {
// Get selected item from the ComboBox
int selIndex = static_cast<int>(SendMessage(hComboBox, CB_GETCURSEL, 0, 0));
if (selIndex != CB_ERR) {
wchar_t buffer[256];
SendMessage(hComboBox, CB_GETLBTEXT, selIndex, (LPARAM)buffer);
// Extract the device number using regex
std::wregex regex(L"\\[(\\d+)\\]");
std::wsmatch match;
std::wstring itemText(buffer);
if (std::regex_search(itemText, match, regex)) {
deviceNumber = match[1];
PopulateListView(hwnd, deviceNumber); // Pass hwnd from WindowProc
}
}
}
else if (LOWORD(wParam) == ID_REFRESH_BUTTON) {
RefreshData();
}
break;
case WM_SIZE:
ResizeControls(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}