#define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #include #include #include #include #include #include #include #include #include #include using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Data::Json; using namespace Windows::Security::Credentials::UI; struct Failure : std::runtime_error { using std::runtime_error::runtime_error; }; void fail(char const* code) { throw Failure(code); } std::vector decode(hstring const& value) { DWORD size=0; if(!CryptStringToBinaryW(value.c_str(),static_cast(value.size()),CRYPT_STRING_BASE64|CRYPT_STRING_STRICT,nullptr,&size,nullptr,nullptr) || size!=32) fail("BIOMETRIC_INVALID_KEY"); std::vector data(size); if(!CryptStringToBinaryW(value.c_str(),static_cast(value.size()),CRYPT_STRING_BASE64|CRYPT_STRING_STRICT,data.data(),&size,nullptr,nullptr)) fail("BIOMETRIC_INVALID_KEY"); return data; } std::string encode(BYTE const* data,DWORD size) { DWORD count=0;check_bool(CryptBinaryToStringA(data,size,CRYPT_STRING_BASE64|CRYPT_STRING_NOCRLF,nullptr,&count)); std::string out(count,'\0');check_bool(CryptBinaryToStringA(data,size,CRYPT_STRING_BASE64|CRYPT_STRING_NOCRLF,out.data(),&count));out.resize(count-1);return out; } LRESULT CALLBACK promptProc(HWND window,UINT message,WPARAM wparam,LPARAM lparam) {return DefWindowProcW(window,message,wparam,lparam);} // UserConsentVerifier must be parented to a window of the calling process: the Electron // HWND belongs to another process, so Hello may fail or open behind the app. Create an // invisible helper-owned window over the app window instead. The helper was started by // the foreground app, so Windows lets it take the foreground for the Hello dialog. HWND promptWindow(HWND owner) { WNDCLASSEXW type{sizeof(type)};type.lpfnWndProc=promptProc;type.hInstance=GetModuleHandleW(nullptr);type.lpszClassName=L"CHJBiometricPrompt";RegisterClassExW(&type); RECT bounds{};GetWindowRect(owner,&bounds); HWND window=CreateWindowExW(WS_EX_TOOLWINDOW|WS_EX_LAYERED|WS_EX_TOPMOST,type.lpszClassName,L"CH-J Server Manager",WS_POPUP,(bounds.left+bounds.right)/2,(bounds.top+bounds.bottom)/2,1,1,nullptr,nullptr,type.hInstance,nullptr); if(!window)fail("BIOMETRIC_FAILED"); SetLayeredWindowAttributes(window,0,0,LWA_ALPHA);ShowWindow(window,SW_SHOWNORMAL);SetForegroundWindow(window); return window; } template void pumpUntilDone(Operation const& operation) { HANDLE done=CreateEventW(nullptr,TRUE,FALSE,nullptr);if(!done)fail("BIOMETRIC_FAILED"); operation.Completed([done](auto&&,auto&&){SetEvent(done);}); for(int tick=0;;tick++) { DWORD wait=MsgWaitForMultipleObjects(1,&done,FALSE,100,QS_ALLINPUT); if(wait==WAIT_OBJECT_0)break; MSG message;while(PeekMessageW(&message,nullptr,0,0,PM_REMOVE)){TranslateMessage(&message);DispatchMessageW(&message);} // Bring the Hello dialog to the front once it appears (first ~3 s). if(tick<30){HWND dialog=FindWindowW(L"Credential Dialog Xaml Host",nullptr);if(dialog)SetForegroundWindow(dialog);} } CloseHandle(done); } void authenticate(JsonObject const& request) { HWND hwnd=reinterpret_cast(std::stoull(to_string(request.GetNamedString(L"hwnd")))); DWORD owner=0;GetWindowThreadProcessId(hwnd,&owner); if(!IsWindow(hwnd) || owner!=static_cast(request.GetNamedNumber(L"pid"))) fail("BIOMETRIC_CANCELLED"); HWND window=promptWindow(hwnd); UserConsentVerificationResult result; try { auto interop=get_activation_factory(); IAsyncOperation operation{nullptr}; auto reason=request.GetNamedString(L"reason"); check_hresult(interop->RequestVerificationForWindowAsync(window,reinterpret_cast(get_abi(reason)),guid_of(),put_abi(operation))); pumpUntilDone(operation);result=operation.GetResults(); } catch(hresult_error const&) {DestroyWindow(window);fail("BIOMETRIC_FAILED");} DestroyWindow(window); if(result==UserConsentVerificationResult::Verified) return; // Includes Hello PIN. if(result==UserConsentVerificationResult::Canceled) fail("BIOMETRIC_CANCELLED"); if(result==UserConsentVerificationResult::DeviceBusy) fail("BIOMETRIC_DEVICE_UNAVAILABLE"); if(result==UserConsentVerificationResult::RetriesExhausted) fail("BIOMETRIC_DEVICE_UNAVAILABLE"); if(result==UserConsentVerificationResult::NotConfiguredForUser) fail("BIOMETRIC_NO_ENROLLMENT"); fail("BIOMETRIC_FAILED"); } JsonObject run(JsonObject const& request) { auto op=request.GetNamedString(L"op");JsonObject response;response.SetNamedValue(L"ok",JsonValue::CreateBooleanValue(true)); if(op==L"status") { auto result=UserConsentVerifier::CheckAvailabilityAsync().get(); response.SetNamedValue(L"available",JsonValue::CreateBooleanValue(result==UserConsentVerifierAvailability::Available)); response.SetNamedValue(L"code",JsonValue::CreateStringValue(result==UserConsentVerifierAvailability::Available?L"BIOMETRIC_AVAILABLE":result==UserConsentVerifierAvailability::NotConfiguredForUser?L"BIOMETRIC_NO_ENROLLMENT":result==UserConsentVerifierAvailability::DeviceBusy?L"BIOMETRIC_DEVICE_UNAVAILABLE":L"BIOMETRIC_UNAVAILABLE"));return response; } if(op==L"authenticate") {authenticate(request);return response;} auto entry=request.GetNamedString(L"entryId");if(entry.size()!=64 || std::wstring(entry).find_first_not_of(L"0123456789abcdef")!=std::wstring::npos)fail("BIOMETRIC_FAILED"); auto target=L"CHJ.ServerManager.Vault.Biometry.v1:"+std::wstring(entry); DATA_BLOB entropy{static_cast(entry.size()*sizeof(wchar_t)),reinterpret_cast(const_cast(entry.c_str()))}; if(op==L"remove") {if(!CredDeleteW(target.c_str(),CRED_TYPE_GENERIC,0) && GetLastError()!=ERROR_NOT_FOUND)fail("BIOMETRIC_CREDENTIAL_FAILED");return response;} if(op==L"store") { auto bytes=decode(request.GetNamedString(L"key"));DATA_BLOB input{static_cast(bytes.size()),bytes.data()},output{}; if(!CryptProtectData(&input,L"CH-J Vault",&entropy,nullptr,nullptr,CRYPTPROTECT_UI_FORBIDDEN,&output)){SecureZeroMemory(bytes.data(),bytes.size());fail("BIOMETRIC_CREDENTIAL_FAILED");} SecureZeroMemory(bytes.data(),bytes.size()); CREDENTIALW credential{};credential.Type=CRED_TYPE_GENERIC;credential.TargetName=target.data();credential.CredentialBlobSize=output.cbData;credential.CredentialBlob=output.pbData;credential.Persist=CRED_PERSIST_LOCAL_MACHINE;credential.UserName=const_cast(L"CH-J Server Manager"); BOOL ok=CredWriteW(&credential,0);SecureZeroMemory(output.pbData,output.cbData);LocalFree(output.pbData);if(!ok)fail("BIOMETRIC_CREDENTIAL_FAILED");return response; } if(op==L"retrieve") { authenticate(request);PCREDENTIALW credential=nullptr; if(!CredReadW(target.c_str(),CRED_TYPE_GENERIC,0,&credential))fail(GetLastError()==ERROR_NOT_FOUND?"BIOMETRIC_ENROLLMENT_INVALIDATED":"BIOMETRIC_CREDENTIAL_FAILED"); DATA_BLOB input{credential->CredentialBlobSize,credential->CredentialBlob},output{}; BOOL ok=CryptUnprotectData(&input,nullptr,&entropy,nullptr,nullptr,CRYPTPROTECT_UI_FORBIDDEN,&output);CredFree(credential); if(!ok)fail("BIOMETRIC_CREDENTIAL_FAILED"); if(output.cbData!=32){SecureZeroMemory(output.pbData,output.cbData);LocalFree(output.pbData);fail("BIOMETRIC_INVALID_KEY");} auto encoded=encode(output.pbData,output.cbData);SecureZeroMemory(output.pbData,output.cbData);LocalFree(output.pbData); response.SetNamedValue(L"key",JsonValue::CreateStringValue(to_hstring(encoded)));SecureZeroMemory(encoded.data(),encoded.size());return response; } fail("BIOMETRIC_FAILED");return response; } int main() { try { init_apartment(apartment_type::multi_threaded); std::string input;std::getline(std::cin,input);if(input.size()>4096)fail("BIOMETRIC_FAILED"); auto request=JsonObject::Parse(to_hstring(input));SecureZeroMemory(input.data(),input.size()); auto response=run(request);std::cout<