/* Copyright 2015-2018 Amebis Copyright 2016 GÉANT This file is part of GÉANTLink. GÉANTLink is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GÉANTLink is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GÉANTLink. If not, see . */ #include "StdAfx.h" using namespace std; using namespace winstd; /// /// Peer initializer /// class wxInitializerPeer { public: /// /// Initialize peer /// wxInitializerPeer(_In_ HINSTANCE instance, _In_opt_ HWND hwndParent); /// /// Uninitialize peer /// virtual ~wxInitializerPeer(); public: wxWindow* m_parent; ///< Parent window protected: static wxCriticalSection s_lock; ///< Initialization lock static unsigned long s_init_ref_count; ///< Initialization reference counter static wxLocale *s_locale; ///< Locale }; ////////////////////////////////////////////////////////////////////// // eap::peer_ttls_ui ////////////////////////////////////////////////////////////////////// eap::peer_ttls_ui::peer_ttls_ui() : peer_ui(eap_type_ttls) { } eap::config_method* eap::peer_ttls_ui::make_config_method() { return new config_method_ttls(*this, 0); } void eap::peer_ttls_ui::config_xml2blob( _In_ DWORD dwFlags, _In_ IXMLDOMNode *pConfigRoot, _Out_ BYTE **pConnectionDataOut, _Out_ DWORD *pdwConnectionDataOutSize) { UNREFERENCED_PARAMETER(dwFlags); // Load configuration from XML. config_connection cfg(*this); cfg.load(pConfigRoot); // Pack configuration. pack(cfg, pConnectionDataOut, pdwConnectionDataOutSize); } void eap::peer_ttls_ui::config_blob2xml( _In_ DWORD dwFlags, _In_count_(dwConnectionDataSize) const BYTE *pConnectionData, _In_ DWORD dwConnectionDataSize, _In_ IXMLDOMDocument *pDoc, _In_ IXMLDOMNode *pConfigRoot) { UNREFERENCED_PARAMETER(dwFlags); // Unpack configuration. config_connection cfg(*this); if (dwConnectionDataSize) unpack(cfg, pConnectionData, dwConnectionDataSize); // Save configuration to XML. cfg.save(pDoc, pConfigRoot); } void eap::peer_ttls_ui::invoke_config_ui( _In_ HWND hwndParent, _In_count_(dwConnectionDataInSize) const BYTE *pConnectionDataIn, _In_ DWORD dwConnectionDataInSize, _Out_ BYTE **ppConnectionDataOut, _Out_ DWORD *pdwConnectionDataOutSize) { // Unpack configuration. config_connection cfg(*this); if (dwConnectionDataInSize) { // Load existing configuration. unpack(cfg, pConnectionDataIn, dwConnectionDataInSize); } else { // This is a blank network profile. `cfg` is already set to defaults. } // Initialize application. wxInitializerPeer init(m_instance, hwndParent); // Create and launch configuration dialog. wxEAPConfigDialog dlg(cfg, init.m_parent); if (!init.m_parent) { FLASHWINFO fwi = { sizeof(FLASHWINFO), dlg.GetHWND(), FLASHW_ALL | FLASHW_TIMERNOFG }; ::FlashWindowEx(&fwi); } if (dlg.ShowModal() != wxID_OK) throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); // Pack new configuration. pack(cfg, ppConnectionDataOut, pdwConnectionDataOutSize); } void eap::peer_ttls_ui::invoke_identity_ui( _In_ HWND hwndParent, _In_ DWORD dwFlags, _In_count_(dwConnectionDataSize) const BYTE *pConnectionData, _In_ DWORD dwConnectionDataSize, _In_count_(dwUserDataSize) const BYTE *pUserData, _In_ DWORD dwUserDataSize, _Out_ BYTE **ppUserDataOut, _Out_ DWORD *pdwUserDataOutSize, _Out_ LPWSTR *ppwszIdentity) { assert(ppwszIdentity); // Unpack configuration. config_connection cfg(*this); unpack(cfg, pConnectionData, dwConnectionDataSize); #if EAP_USE_NATIVE_CREDENTIAL_CACHE // Unpack cached credentials. credentials_connection cred_in(*this, cfg); if (dwUserDataSize) unpack(cred_in, pUserData, dwUserDataSize); #else UNREFERENCED_PARAMETER(pUserData); UNREFERENCED_PARAMETER(dwUserDataSize); #endif credentials_connection cred_out(*this, cfg); config_provider *cfg_prov = NULL; config_method_ttls *cfg_method = NULL; // Initialize application. wxInitializerPeer init(m_instance, hwndParent); if (cfg.m_providers.size() > 1) { // Multiple identity providers: User has to select one first. wxEAPProviderSelectDialog dlg(cfg, init.m_parent); // Centre and display dialog. dlg.Centre(wxBOTH); if (!init.m_parent) { FLASHWINFO fwi = { sizeof(FLASHWINFO), dlg.GetHWND(), FLASHW_ALL | FLASHW_TIMERNOFG }; ::FlashWindowEx(&fwi); } if (dlg.ShowModal() != wxID_OK) throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); cfg_prov = dlg.GetSelection(); assert(cfg_prov); } else if (!cfg.m_providers.empty()) { // Single identity provider. No need to ask user to select one. cfg_prov = &cfg.m_providers.front(); } else { // No identity provider. Bail out. throw invalid_argument(__FUNCTION__ " Configuration has no identity providers."); } // The identity provider is selected. assert(cfg_prov); cfg_method = dynamic_cast(cfg_prov->m_methods.front().get()); assert(cfg_method); // Configure output credentials. cred_out.m_namespace = cfg_prov->m_namespace; cred_out.m_id = cfg_prov->m_id; auto cred = dynamic_cast(cfg_method->make_credentials()); cred_out.m_cred.reset(cred); #if EAP_USE_NATIVE_CREDENTIAL_CACHE bool has_cached = cred_in.m_cred && cred_in.match(*cfg_prov); #endif if (dwFlags & EAP_FLAG_GUEST_ACCESS) { // Disable credential saving for guests. cfg_method-> m_allow_save = false; cfg_method->m_inner->m_allow_save = false; } // Combine outer credentials. wstring target_name(std::move(cfg_prov->get_id())); eap::credentials::source_t src_outer = cred->credentials_tls::combine( dwFlags, NULL, #if EAP_USE_NATIVE_CREDENTIAL_CACHE has_cached ? cred_in.m_cred.get() : NULL, #else NULL, #endif *cfg_method, cfg_method->m_allow_save ? target_name.c_str() : NULL); if (src_outer == eap::credentials::source_unknown || src_outer != eap::credentials::source_config && eap::config_method::status_cred_begin <= cfg_method->m_last_status && cfg_method->m_last_status < eap::config_method::status_cred_end) { // Build dialog to prompt for outer credentials. wxEAPCredentialsDialog dlg(*cfg_prov, init.m_parent); if (eap::config_method::status_cred_begin <= cfg_method->m_last_status && cfg_method->m_last_status < eap::config_method::status_cred_end) dlg.AddContent(new wxEAPCredentialWarningPanel(*cfg_prov, cfg_method->m_last_status, &dlg)); auto panel = new wxTLSCredentialsPanel(*cfg_prov, *cfg_method, *cred, &dlg, false); panel->SetRemember(src_outer == eap::credentials::source_storage); dlg.AddContent(panel); // Update dialog layout. dlg.Layout(); dlg.GetSizer()->Fit(&dlg); // Centre and display dialog. dlg.Centre(wxBOTH); if (!init.m_parent) { FLASHWINFO fwi = { sizeof(FLASHWINFO), dlg.GetHWND(), FLASHW_ALL | FLASHW_TIMERNOFG }; ::FlashWindowEx(&fwi); } if (dlg.ShowModal() != wxID_OK) throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); if (panel->GetRemember()) { // Write credentials to credential manager. try { cred->credentials_tls::store(target_name.c_str(), 0); } catch (winstd::win_runtime_error &err) { wxLogError(winstd::tstring_printf(_("Error writing credentials to Credential Manager: %hs (error %u)"), err.what(), err.number()).c_str()); } catch (...) { wxLogError(_("Writing credentials failed.")); } } } // Combine inner credentials. eap::credentials::source_t src_inner = cred->m_inner->combine( dwFlags, NULL, #if EAP_USE_NATIVE_CREDENTIAL_CACHE has_cached ? dynamic_cast(cred_in.m_cred.get())->m_inner.get() : NULL, #else NULL, #endif *cfg_method->m_inner, cfg_method->m_inner->m_allow_save ? target_name.c_str() : NULL); if (src_inner == eap::credentials::source_unknown || src_inner != eap::credentials::source_config && eap::config_method::status_cred_begin <= cfg_method->m_inner->m_last_status && cfg_method->m_inner->m_last_status < eap::config_method::status_cred_end) { // Prompt for inner credentials. #if EAP_INNER_EAPHOST auto cfg_inner_eaphost = dynamic_cast(cfg_method->m_inner.get()); if (!cfg_inner_eaphost) #endif { // Native inner methods. Build dialog to prompt for inner credentials. wxEAPCredentialsDialog dlg(*cfg_prov, init.m_parent); if (eap::config_method::status_cred_begin <= cfg_method->m_inner->m_last_status && cfg_method->m_inner->m_last_status < eap::config_method::status_cred_end) dlg.AddContent(new wxEAPCredentialWarningPanel(*cfg_prov, cfg_method->m_inner->m_last_status, &dlg)); wxEAPCredentialsPanelBase *panel = NULL; switch (cfg_method->m_inner->get_method_id()) { case eap_type_legacy_pap : panel = new wxPAPCredentialsPanel (*cfg_prov, *dynamic_cast(cfg_method->m_inner.get()), *dynamic_cast(cred->m_inner.get()), &dlg, false); break; case eap_type_legacy_mschapv2: panel = new wxMSCHAPv2CredentialsPanel(*cfg_prov, *dynamic_cast(cfg_method->m_inner.get()), *dynamic_cast(cred->m_inner.get()), &dlg, false); break; case eap_type_mschapv2 : panel = new wxMSCHAPv2CredentialsPanel(*cfg_prov, *dynamic_cast(cfg_method->m_inner.get()), *dynamic_cast(cred->m_inner.get()), &dlg, false); break; case eap_type_gtc : { // EAP-GTC credential prompt differes for "Challenge/Response" and "Password" authentication modes. eap::credentials_identity *cred_resp; eap::credentials_pass *cred_pass; if ((cred_resp = dynamic_cast(cred->m_inner.get())) != NULL) panel = new wxGTCResponseCredentialsPanel(*cfg_prov, *dynamic_cast(cfg_method->m_inner.get()), *cred_resp, &dlg, false); else if ((cred_pass = dynamic_cast(cred->m_inner.get())) != NULL) panel = new wxGTCPasswordCredentialsPanel(*cfg_prov, *dynamic_cast(cfg_method->m_inner.get()), *cred_pass, &dlg, false); else wxLogError("Unsupported authentication mode."); break; } default : wxLogError("Unsupported inner authentication method."); } panel->SetRemember(src_inner == eap::credentials::source_storage); dlg.AddContent(panel); // Update dialog layout. dlg.Layout(); dlg.GetSizer()->Fit(&dlg); // Centre and display dialog. dlg.Centre(wxBOTH); if (!init.m_parent) { FLASHWINFO fwi = { sizeof(FLASHWINFO), dlg.GetHWND(), FLASHW_ALL | FLASHW_TIMERNOFG }; ::FlashWindowEx(&fwi); } if (dlg.ShowModal() != wxID_OK) throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); // Write credentials to credential manager. if (panel->GetRemember()) { try { cred->m_inner->store(target_name.c_str(), 1); } catch (winstd::win_runtime_error &err) { wxLogError(winstd::tstring_printf(_("Error writing credentials to Credential Manager: %hs (error %u)"), err.what(), err.number()).c_str()); } catch (...) { wxLogError(_("Writing credentials failed.")); } } } #if EAP_INNER_EAPHOST else { // EapHost inner method auto cred_inner = dynamic_cast(cred->m_inner.get()); DWORD cred_data_size = 0; winstd::eap_blob cred_data; unique_ptr identity; winstd::eap_error error; DWORD dwResult = EapHostPeerInvokeIdentityUI( 0, cfg_inner_eaphost->get_type(), dwFlags, hwndParent, (DWORD)cfg_inner_eaphost->m_cfg_blob.size(), cfg_inner_eaphost->m_cfg_blob.data(), (DWORD)cred_inner->m_cred_blob.size(), cred_inner->m_cred_blob.data(), &cred_data_size, get_ptr(cred_data), get_ptr(identity), get_ptr(error), NULL); if (dwResult == ERROR_SUCCESS) { // Inner EAP method provided credentials. cred_inner->m_identity = identity.get(); BYTE *_cred_data = cred_data.get(); cred_inner->m_cred_blob.assign(_cred_data, _cred_data + cred_data_size); SecureZeroMemory(_cred_data, cred_data_size); // TODO: If we ever choose to store EapHost credentials to Windows Credential Manager, add a "Save credentials? Yes/No" prompt here and write them to Credential Manager. } else if (dwResult == ERROR_CANCELLED) { // Not really an error. throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); } else if (error) { wxLogError(_("Invoking EAP identity UI failed (error %u, %s, %s)."), error->dwWinError, error->pRootCauseString, error->pRepairString); throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerInvokeIdentityUI failed."); } else { wxLogError(_("Invoking EAP identity UI failed (error %u)."), dwResult); throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerInvokeIdentityUI failed."); } } #endif } // Build our identity. ;) wstring identity(std::move(cfg_method->get_public_identity(*dynamic_cast(cred_out.m_cred.get())))); log_event(&EAPMETHOD_TRACE_EVT_CRED_OUTER_ID1, event_data((unsigned int)eap_type_ttls), event_data(identity), event_data::blank); size_t size = sizeof(WCHAR)*(identity.length() + 1); *ppwszIdentity = (WCHAR*)alloc_memory(size); memcpy(*ppwszIdentity, identity.c_str(), size); // Pack credentials. pack(cred_out, ppUserDataOut, pdwUserDataOutSize); } void eap::peer_ttls_ui::invoke_interactive_ui( _In_ HWND hwndParent, _In_count_(dwUIContextDataSize) const BYTE *pUIContextData, _In_ DWORD dwUIContextDataSize, _Inout_ BYTE **ppDataFromInteractiveUI, _Inout_ DWORD *pdwDataFromInteractiveUISize) { // Unpack context data. config_connection cfg(*this); credentials_connection cred(*this, cfg); ui_context_ttls ctx(cfg, cred); unpack(ctx, pUIContextData, dwUIContextDataSize); // Look-up the provider. config_provider *cfg_prov; config_method_ttls *cfg_method; for (auto _cfg_prov = cfg.m_providers.begin(), cfg_prov_end = cfg.m_providers.end();; ++_cfg_prov) { if (_cfg_prov != cfg_prov_end) { if (cred.match(*_cfg_prov)) { // Matching provider found. if (_cfg_prov->m_methods.empty()) throw invalid_argument(string_printf(__FUNCTION__ " %ls provider has no methods.", _cfg_prov->get_id().c_str())); cfg_prov = &*_cfg_prov; cfg_method = dynamic_cast(_cfg_prov->m_methods.front().get()); break; } } else throw invalid_argument(string_printf(__FUNCTION__ " Credentials do not match to any provider within this connection configuration (provider: %ls).", cred.get_id().c_str())); } #if EAP_INNER_EAPHOST auto cfg_inner_eaphost = dynamic_cast(cfg_method->m_inner.get()); if (!cfg_inner_eaphost) #endif { // Initialize application. wxInitializerPeer init(m_instance, hwndParent); sanitizing_wstring challenge(reinterpret_cast(ctx.m_data.data()), ctx.m_data.size()/sizeof(sanitizing_wstring::value_type)), response; // Build dialog to prompt for response. wxGTCResponseDialog dlg(*cfg_prov, init.m_parent); auto panel = new wxGTCResponsePanel(response, challenge.c_str(), &dlg); dlg.AddContent(panel); // Update dialog layout. dlg.Layout(); dlg.GetSizer()->Fit(&dlg); // Centre and display dialog. dlg.Centre(wxBOTH); if (!init.m_parent) { FLASHWINFO fwi = { sizeof(FLASHWINFO), dlg.GetHWND(), FLASHW_ALL | FLASHW_TIMERNOFG }; ::FlashWindowEx(&fwi); } if (dlg.ShowModal() != wxID_OK) throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); // Save response. ctx.m_data.assign( reinterpret_cast(response.data() ), reinterpret_cast(response.data() + response.length())); } #if EAP_INNER_EAPHOST else { // EapHost inner method DWORD dwSizeofDataFromInteractiveUI; BYTE *pDataFromInteractiveUI; winstd::eap_error error; DWORD dwResult = EapHostPeerInvokeInteractiveUI( hwndParent, (DWORD)ctx.m_data.size(), ctx.m_data.data(), &dwSizeofDataFromInteractiveUI, &pDataFromInteractiveUI, get_ptr(error)); if (dwResult == ERROR_SUCCESS) { // Inner EAP method provided response. ctx.m_data.assign(pDataFromInteractiveUI, pDataFromInteractiveUI + dwSizeofDataFromInteractiveUI); } else if (dwResult == ERROR_CANCELLED) { // Not really an error. throw win_runtime_error(ERROR_CANCELLED, __FUNCTION__ " Cancelled."); } else if (error) { wxLogError(_("Invoking EAP interactive UI failed (error %u, %s, %s)."), error->dwWinError, error->pRootCauseString, error->pRepairString); throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerInvokeInteractiveUI failed."); } else { wxLogError(_("Invoking EAP interactive UI failed (error %u)."), dwResult); throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerInvokeInteractiveUI failed."); } } #endif // Pack output data. pack(ctx.m_data, ppDataFromInteractiveUI, pdwDataFromInteractiveUISize); } ////////////////////////////////////////////////////////////////////// // wxInitializerPeer ////////////////////////////////////////////////////////////////////// wxInitializerPeer::wxInitializerPeer(_In_ HINSTANCE instance, _In_opt_ HWND hwndParent) { wxCriticalSectionLocker locker(s_lock); if (s_init_ref_count++ == 0) { // Initialize application. new wxApp(); wxEntryStart(instance); // Do our wxWidgets configuration and localization initialization. wxInitializeConfig(); s_locale = new wxLocale; if (wxInitializeLocale(*s_locale)) { s_locale->AddCatalog(wxT("wxExtend") wxT(wxExtendVersion)); s_locale->AddCatalog(wxT("EAPTTLSUI")); } } if (hwndParent) { // Create wxWidget-approved parent window. m_parent = new wxWindow; m_parent->SetHWND((WXHWND)hwndParent); m_parent->AdoptAttributesFromHWND(); wxTopLevelWindows.Append(m_parent); } else m_parent = NULL; } wxInitializerPeer::~wxInitializerPeer() { wxCriticalSectionLocker locker(s_lock); if (m_parent) { wxTopLevelWindows.DeleteObject(m_parent); m_parent->SetHWND((WXHWND)NULL); } if (--s_init_ref_count == 0) { wxEntryCleanup(); if (s_locale) { delete s_locale; s_locale = NULL; } } } wxCriticalSection wxInitializerPeer::s_lock; unsigned long wxInitializerPeer::s_init_ref_count = 0; wxLocale *wxInitializerPeer::s_locale = NULL;