From 88651e4ffe91088122c65d14b64e5d4a966c3431 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Fri, 4 Nov 2016 09:16:43 +0100 Subject: [PATCH] EAP-MSCHAPv2 support finished --- lib/MSCHAPv2/include/MSCHAPv2.h | 33 ++- lib/MSCHAPv2/include/Method.h | 199 ++++++++++++---- lib/MSCHAPv2/src/MSCHAPv2.cpp | 12 +- lib/MSCHAPv2/src/Method.cpp | 408 ++++++++++++++++++++++---------- lib/TTLS/src/Config.cpp | 16 +- lib/TTLS/src/Method.cpp | 2 +- lib/TTLS/src/Module.cpp | 8 +- lib/TTLS_UI/include/TTLS_UI.h | 7 +- lib/TTLS_UI/src/Module.cpp | 14 +- lib/TTLS_UI/src/TTLS_UI.cpp | 22 +- 10 files changed, 508 insertions(+), 213 deletions(-) diff --git a/lib/MSCHAPv2/include/MSCHAPv2.h b/lib/MSCHAPv2/include/MSCHAPv2.h index ff11d34..0184b3d 100644 --- a/lib/MSCHAPv2/include/MSCHAPv2.h +++ b/lib/MSCHAPv2/include/MSCHAPv2.h @@ -23,6 +23,8 @@ namespace eap { + enum chap_packet_code_t; + struct WINSTD_NOVTABLE chap_header; struct WINSTD_NOVTABLE challenge_mschapv2; struct WINSTD_NOVTABLE challenge_hash; struct WINSTD_NOVTABLE nt_password_hash; @@ -59,9 +61,34 @@ namespace eap /// \addtogroup MSCHAPv2 /// @{ + /// + /// CHAP packet codes + /// + #pragma warning(suppress: 4480) + enum chap_packet_code_t : unsigned char { + chap_packet_code_challenge = 1, ///< Challenge + chap_packet_code_response = 2, ///< Response + chap_packet_code_success = 3, ///< Success + chap_packet_code_failure = 4, ///< Failure + + mschapv2_packet_code_change_password = 7, ///< Change password + }; + + #pragma pack(push) #pragma pack(1) + /// + /// CHAP packet header base class + /// + struct WINSTD_NOVTABLE chap_header + { + chap_packet_code_t code; ///< CHAP packet code + unsigned char ident; ///< CHAP identifier + unsigned char length[2]; ///< CHAP packet length + }; + + /// /// MSCHAPv2 Challenge /// @@ -98,7 +125,7 @@ namespace eap /// challenge_hash( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username); @@ -195,7 +222,7 @@ namespace eap /// nt_response( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username, _In_z_ const wchar_t *password); @@ -242,7 +269,7 @@ namespace eap /// authenticator_response( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username, _In_z_ const wchar_t *password, diff --git a/lib/MSCHAPv2/include/Method.h b/lib/MSCHAPv2/include/Method.h index 490a9c6..c12dd87 100644 --- a/lib/MSCHAPv2/include/Method.h +++ b/lib/MSCHAPv2/include/Method.h @@ -20,7 +20,9 @@ namespace eap { + class method_mschapv2_base; class method_mschapv2; + class method_mschapv2_diameter; } #pragma once @@ -38,10 +40,111 @@ namespace eap /// \addtogroup EAPBaseMethod /// @{ + /// + /// MSCHAPv2 method base class + /// + class method_mschapv2_base : public method + { + WINSTD_NONCOPYABLE(method_mschapv2_base) + + public: + /// + /// Constructs a MSCHAPv2 method + /// + /// \param[in] mod MSCHAPv2 module to use for global services + /// \param[in] cfg Method configuration + /// \param[in] cred User credentials + /// + method_mschapv2_base(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred); + + /// + /// Moves a MSCHAPv2 method + /// + /// \param[in] other MSCHAPv2 method to move from + /// + method_mschapv2_base(_Inout_ method_mschapv2_base &&other); + + /// + /// Moves a MSCHAPv2 method + /// + /// \param[in] other MSCHAPv2 method to move from + /// + /// \returns Reference to this object + /// + method_mschapv2_base& operator=(_Inout_ method_mschapv2_base &&other); + + /// \name Session management + /// @{ + + virtual void begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD); + + /// @} + + /// \name Packet processing + /// @{ + + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + /// @} + + virtual void get_result( + _In_ EapPeerMethodResultReason reason, + _Out_ EapPeerMethodResult *pResult); + + protected: + /// + /// Processes MSCHAPv2 success message + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 5. Success Packet)](https://tools.ietf.org/html/rfc2759#section-5) + /// + /// \param[in] argv List of message values + /// + void process_success(_In_ const std::list &argv); + + /// + /// Processes MSCHAPv2 error message + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 6. Failure Packet)](https://tools.ietf.org/html/rfc2759#section-6) + /// + /// \param[in] argv List of message values + /// + void process_error(_In_ const std::list &argv); + + /// + /// Splits MSCHAPv2 success or error messages + /// + /// \param[in] resp MSCHAPv2 success or error message (i.e. "E=648 R=1 C=d86e0aa6cb5539e5fb31dd5dc5f6898c V=3 M=Password Expired") + /// \param[in] count Number of characters in \p resp + /// + /// \returns A list of individual parts of \p resp message (i.e. ("E=648", "R=1", "C=d86e0aa6cb5539e5fb31dd5dc5f6898c", "V=3", "M=Password Expired")) + /// + static std::list parse_response(_In_count_(count) const char *resp, _In_ size_t count); + + protected: + config_method_mschapv2 &m_cfg; ///< Method configuration + credentials_pass &m_cred; ///< Method user credentials + winstd::crypt_prov m_cp; ///< Cryptography provider for general services + + sanitizing_blob m_challenge_server; ///< MSCHAP server challenge + challenge_mschapv2 m_challenge_client; ///< MSCHAP client challenge + unsigned char m_ident; ///< Ident + nt_response m_nt_resp; ///< NT-Response + bool m_success; ///< Did we receive MS-CHAP2-Success? + + sanitizing_blob m_packet_res; ///< Response packet + }; + + /// /// MSCHAPv2 method /// - class method_mschapv2 : public method + class method_mschapv2 : public method_mschapv2_base { WINSTD_NONCOPYABLE(method_mschapv2) @@ -71,6 +174,52 @@ namespace eap /// method_mschapv2& operator=(_Inout_ method_mschapv2 &&other); + /// \name Packet processing + /// @{ + + virtual EapPeerMethodResponseAction process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize); + + /// @} + }; + + /// @} + + + /// + /// MSCHAPv2 method over Diameter AVP (for use as inner EAP-TTLS) + /// + class method_mschapv2_diameter : public method_mschapv2_base + { + WINSTD_NONCOPYABLE(method_mschapv2_diameter) + + public: + /// + /// Constructs a MSCHAPv2 method + /// + /// \param[in] mod MSCHAPv2 module to use for global services + /// \param[in] cfg Method configuration + /// \param[in] cred User credentials + /// + method_mschapv2_diameter(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred); + + /// + /// Moves a MSCHAPv2 method + /// + /// \param[in] other MSCHAPv2 method to move from + /// + method_mschapv2_diameter(_Inout_ method_mschapv2_diameter &&other); + + /// + /// Moves a MSCHAPv2 method + /// + /// \param[in] other MSCHAPv2 method to move from + /// + /// \returns Reference to this object + /// + method_mschapv2_diameter& operator=(_Inout_ method_mschapv2_diameter &&other); + /// \name Session management /// @{ @@ -89,16 +238,8 @@ namespace eap _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, _In_ DWORD dwReceivedPacketSize); - virtual void get_response_packet( - _Out_ sanitizing_blob &packet, - _In_opt_ DWORD size_max = MAXDWORD); - /// @} - virtual void get_result( - _In_ EapPeerMethodResultReason reason, - _Out_ EapPeerMethodResult *pResult); - friend class method_ttls; // Setting of initial challenge derived from TLS PRF protected: @@ -110,45 +251,7 @@ namespace eap /// void process_packet(_In_bytecount_(size_pck) const void *pck, _In_ size_t size_pck); - /// - /// Processes MS-CHAP2-Success AVP - /// - /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 5. Success Packet)](https://tools.ietf.org/html/rfc2759#section-5) - /// - /// \param[in] argv List of message values - /// - void process_success(_In_ const std::list &argv); - - /// - /// Processes MS-CHAP-Error AVP - /// - /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 6. Failure Packet)](https://tools.ietf.org/html/rfc2759#section-6) - /// - /// \param[in] argv List of message values - /// - void process_error(_In_ const std::list &argv); - - /// - /// Splits MS-CHAP2-Success or MS-CHAP-Error messages - /// - /// \param[in] resp MS-CHAP2-Success or MS-CHAP-Error message (i.e. "E=648 R=1 C=d86e0aa6cb5539e5fb31dd5dc5f6898c V=3 M=Password Expired") - /// \param[in] count Number of characters in \p resp - /// - /// \returns A list of individual parts of \p resp message (i.e. ("E=648", "R=1", "C=d86e0aa6cb5539e5fb31dd5dc5f6898c", "V=3", "M=Password Expired")) - /// - static std::list parse_response(_In_count_(count) const char *resp, _In_ size_t count); - protected: - config_method_mschapv2 &m_cfg; ///< Method configuration - credentials_pass &m_cred; ///< Method user credentials - winstd::crypt_prov m_cp; ///< Cryptography provider for general services - - challenge_mschapv2 m_challenge_server; ///< MSCHAP server challenge - challenge_mschapv2 m_challenge_client; ///< MSCHAP client challenge - unsigned char m_ident; ///< Ident - nt_response m_nt_resp; ///< NT-Response - bool m_success; ///< Did we receive MS-CHAP2-Success? - /// /// Communication phase /// @@ -158,8 +261,6 @@ namespace eap phase_challenge_server, ///< Verify server challenge phase_finished, ///< Connection shut down } m_phase; ///< What phase is our communication at? - - sanitizing_blob m_packet_res; ///< Response packet }; /// @} diff --git a/lib/MSCHAPv2/src/MSCHAPv2.cpp b/lib/MSCHAPv2/src/MSCHAPv2.cpp index be60ca7..caf0ebf 100644 --- a/lib/MSCHAPv2/src/MSCHAPv2.cpp +++ b/lib/MSCHAPv2/src/MSCHAPv2.cpp @@ -99,16 +99,16 @@ eap::challenge_hash::challenge_hash() eap::challenge_hash::challenge_hash( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username) { crypt_hash hash; if (!hash.create(cp, CALG_SHA)) throw win_runtime_error(__FUNCTION__ " Creating SHA hash failed."); - if (!CryptHashData(hash, (const BYTE*)&challenge_client, (DWORD)sizeof(challenge_client), 0) || - !CryptHashData(hash, (const BYTE*)&challenge_server, (DWORD)sizeof(challenge_server), 0) || - !CryptHashData(hash, (const BYTE*)username , (DWORD)strlen(username) , 0)) + if (!CryptHashData(hash, (const BYTE*)&challenge_client , (DWORD)sizeof(challenge_client), 0) || + !CryptHashData(hash, challenge_server.data(), (DWORD)challenge_server.size() , 0) || + !CryptHashData(hash, (const BYTE*)username , (DWORD)strlen(username) , 0)) throw win_runtime_error(__FUNCTION__ " Error hashing data."); unsigned char hash_val[20]; DWORD size_hash_val = sizeof(hash_val); @@ -201,7 +201,7 @@ eap::nt_response::nt_response() eap::nt_response::nt_response( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username, _In_z_ const wchar_t *password) @@ -270,7 +270,7 @@ eap::authenticator_response::authenticator_response() eap::authenticator_response::authenticator_response( _In_ HCRYPTPROV cp, - _In_ const challenge_mschapv2 &challenge_server, + _In_ const sanitizing_blob &challenge_server, _In_ const challenge_mschapv2 &challenge_client, _In_z_ const char *username, _In_z_ const wchar_t *password, diff --git a/lib/MSCHAPv2/src/Method.cpp b/lib/MSCHAPv2/src/Method.cpp index dd70369..2519ad0 100644 --- a/lib/MSCHAPv2/src/Method.cpp +++ b/lib/MSCHAPv2/src/Method.cpp @@ -25,21 +25,20 @@ using namespace winstd; ////////////////////////////////////////////////////////////////////// -// eap::method_mschapv2 +// eap::method_mschapv2_base ////////////////////////////////////////////////////////////////////// -eap::method_mschapv2::method_mschapv2(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred) : +eap::method_mschapv2_base::method_mschapv2_base(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred) : m_cfg(cfg), m_cred(cred), m_ident(0), m_success(false), - m_phase(phase_unknown), method(mod) { } -eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : +eap::method_mschapv2_base::method_mschapv2_base(_Inout_ method_mschapv2_base &&other) : m_cfg ( other.m_cfg ), m_cred ( other.m_cred ), m_cp (std::move(other.m_cp )), @@ -48,14 +47,13 @@ eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : m_ident (std::move(other.m_ident )), m_nt_resp (std::move(other.m_nt_resp )), m_success (std::move(other.m_success )), - m_phase (std::move(other.m_phase )), m_packet_res (std::move(other.m_packet_res )), method (std::move(other )) { } -eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 &&other) +eap::method_mschapv2_base& eap::method_mschapv2_base::operator=(_Inout_ method_mschapv2_base &&other) { if (this != std::addressof(other)) { assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method within same configuration only! @@ -67,7 +65,6 @@ eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 && m_ident = std::move(other.m_ident ); m_nt_resp = std::move(other.m_nt_resp ); m_success = std::move(other.m_success ); - m_phase = std::move(other.m_phase ); m_packet_res = std::move(other.m_packet_res ); } @@ -75,7 +72,7 @@ eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 && } -void eap::method_mschapv2::begin_session( +void eap::method_mschapv2_base::begin_session( _In_ DWORD dwFlags, _In_ const EapAttributes *pAttributeArray, _In_ HANDLE hTokenImpersonateUser, @@ -91,12 +88,283 @@ void eap::method_mschapv2::begin_session( // Create cryptographics provider for support needs (client challenge ...). if (!m_cp.create(NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) throw win_runtime_error(__FUNCTION__ " Error creating cryptographics provider."); +} + + +void eap::method_mschapv2_base::get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) +{ + if (m_packet_res.size() > size_max) + throw invalid_argument(string_printf(__FUNCTION__ " This method does not support packet fragmentation, but the data size is too big to fit in one packet (packet: %u, maximum: %u).", m_packet_res.size(), size_max).c_str()); + + packet.assign(m_packet_res.begin(), m_packet_res.end()); +} + + +void eap::method_mschapv2_base::get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *pResult) +{ + assert(pResult); + + method::get_result(reason, pResult); + + if (reason == EapPeerMethodResultSuccess) + m_cfg.m_last_status = config_method::status_success; + + // Always ask EAP host to save the connection data. And it will save it *only* when we report "success". + // Don't worry. EapHost is well aware of failed authentication condition. + pResult->fSaveConnectionData = TRUE; + pResult->fIsSuccess = TRUE; +} + + +void eap::method_mschapv2_base::process_success(_In_ const list &argv) +{ + m_success = false; + + for (auto arg = argv.cbegin(), arg_end = argv.cend(); arg != arg_end; ++arg) { + const string &val = *arg; + if ((val[0] == 'S' || val[0] == 's') && val[1] == '=') { + // "S=" + hex_dec dec; + sanitizing_blob resp; + bool is_last; + dec.decode(resp, is_last, val.data() + 2, (size_t)-1); + + // Calculate expected authenticator response. + sanitizing_string identity_utf8; + WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); + authenticator_response resp_exp(m_cp, m_challenge_server, m_challenge_client, identity_utf8.c_str(), m_cred.m_password.c_str(), m_nt_resp); + + // Compare against provided authemticator response. + if (resp.size() != sizeof(resp_exp) || memcmp(resp.data(), &resp_exp, sizeof(resp_exp)) != 0) + throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string failed."); + + m_module.log_event(&EAPMETHOD_METHOD_SUCCESS, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank); + m_success = true; + } + } + + if (!m_success) + throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string not found."); +} + + +void eap::method_mschapv2_base::process_error(_In_ const list &argv) +{ + for (auto arg = argv.cbegin(), arg_end = argv.cend(); arg != arg_end; ++arg) { + const string &val = *arg; + if ((val[0] == 'E' || val[0] == 'e') && val[1] == '=') { + DWORD dwResult = strtoul(val.data() + 2, NULL, 10); + m_module.log_event(&EAPMETHOD_METHOD_FAILURE_ERROR, event_data((unsigned int)m_cfg.get_method_id()), event_data(dwResult), event_data::blank); + switch (dwResult) { + case ERROR_ACCT_DISABLED : m_cfg.m_last_status = config_method::status_account_disabled ; break; + case ERROR_RESTRICTED_LOGON_HOURS: m_cfg.m_last_status = config_method::status_account_logon_hours; break; + case ERROR_NO_DIALIN_PERMISSION : m_cfg.m_last_status = config_method::status_account_denied ; break; + case ERROR_PASSWD_EXPIRED : m_cfg.m_last_status = config_method::status_cred_expired ; break; + case ERROR_CHANGING_PASSWORD : m_cfg.m_last_status = config_method::status_cred_changing ; break; + default : m_cfg.m_last_status = config_method::status_cred_invalid ; + } + } else if ((val[0] == 'C' || val[0] == 'c') && val[1] == '=') { + hex_dec dec; + sanitizing_blob resp; + bool is_last; + dec.decode(resp, is_last, val.data() + 2, (size_t)-1); + if (resp.size() != sizeof(m_challenge_server)) + throw invalid_argument(string_printf(__FUNCTION__ " Incorrect MSCHAPv2 challenge length (expected: %uB, received: %uB).", sizeof(m_challenge_server), resp.size()).c_str()); + memcpy(&m_challenge_server, resp.data(), sizeof(m_challenge_server)); + } else if ((val[0] == 'M' || val[0] == 'm') && val[1] == '=') { + MultiByteToWideChar(CP_UTF8, 0, val.data() + 2, -1, m_cfg.m_last_msg); + m_module.log_event(&EAPMETHOD_METHOD_FAILURE_ERROR1, event_data((unsigned int)m_cfg.get_method_id()), event_data(m_cfg.m_last_msg), event_data::blank); + } + } +} + + +list eap::method_mschapv2_base::parse_response(_In_count_(count) const char *resp, _In_ size_t count) +{ + list argv; + + for (size_t i = 0; i < count && resp[i]; ) { + if (i + 1 < count && (resp[i] == 'M' || resp[i] == 'm') && resp[i + 1] == '=') { + // The message is always the last value. It may contain spaces and it spans to the end. + argv.push_back(string(resp + i, strnlen(resp + i, count - i))); + break; + } else if (!isspace(resp[i])) { + // Search for the next space and add value up to it. + size_t j; + for (j = i + 1; j < count && resp[j] && !isspace(resp[j]); j++); + argv.push_back(string(resp + i, j - i)); + i = j + 1; + } else { + // Skip (multiple) spaces. + i++; + } + } + + return argv; +} + + +////////////////////////////////////////////////////////////////////// +// eap::method_mschapv2 +////////////////////////////////////////////////////////////////////// + +eap::method_mschapv2::method_mschapv2(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred) : + method_mschapv2_base(mod, cfg, cred) +{ +} + + +eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : + method_mschapv2_base(std::move(other )) +{ +} + + +eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 &&other) +{ + if (this != std::addressof(other)) + (method_mschapv2_base&)*this = std::move(other); + + return *this; +} + + +EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize) +{ + assert(pReceivedPacket || dwReceivedPacketSize == 0); + + for (const unsigned char *pck = reinterpret_cast(pReceivedPacket), *pck_end = pck + dwReceivedPacketSize; pck < pck_end; ) { + if (pck + sizeof(chap_header) > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete CHAP header."); + auto hdr = reinterpret_cast(pck); + unsigned short length = ntohs(*reinterpret_cast(hdr->length)); + const unsigned char + *msg = reinterpret_cast(hdr + 1), + *msg_end = pck + length; + if (msg_end > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete CHAP data."); + + // Save packet ident. + m_ident = hdr->ident; + + switch (hdr->code) { + case chap_packet_code_challenge: { + m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_mschapv2), event_data::blank); + + if (msg + 1 > msg_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete CHAP challenge packet."); + + // Read server challenge. + if (msg + 1 + msg[0] > msg_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete CHAP server challenge."); + m_challenge_server.assign(msg + 1, msg + 1 + msg[0]); + + // Randomize Peer-Challenge. + m_challenge_client.randomize(m_cp); + + // Calculate NT-Response. + sanitizing_string identity_utf8; + WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); + m_nt_resp = nt_response(m_cp, m_challenge_server, m_challenge_client, identity_utf8.c_str(), m_cred.m_password.c_str()); + + // Prepare CHAP response value. + sanitizing_blob value; + value.reserve( + sizeof(challenge_mschapv2) + // Peer-Challenge + 8 + // Reserved + sizeof(nt_response) + // NT-Response + 1); // Flags + value.insert(value.end(), reinterpret_cast(&m_challenge_client), reinterpret_cast(&m_challenge_client + 1)); // Peer-Challenge + value.insert(value.end(), 8, 0); // Reserved (must be zero) + value.insert(value.end(), reinterpret_cast(&m_nt_resp), reinterpret_cast(&m_nt_resp + 1)); // NT-Response + value.push_back(0); // Flags + + chap_header hdr_resp; + hdr_resp.code = chap_packet_code_response; + hdr_resp.ident = m_ident; + size_t size_value = value.size(); + *reinterpret_cast(hdr_resp.length) = htons((unsigned short)(sizeof(chap_header) + 1 + size_value + identity_utf8.length())); + assert(size_value <= 0xff); // CHAP value can be 255B max + + // Append response. + m_packet_res.assign(reinterpret_cast(&hdr_resp), reinterpret_cast(&hdr_resp + 1)); + m_packet_res.insert(m_packet_res.end(), 1, (unsigned char)size_value); + m_packet_res.insert(m_packet_res.end(), value.begin(), value.end()); + m_packet_res.insert(m_packet_res.end(), identity_utf8.begin(), identity_utf8.end()); + + m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point. + return EapPeerMethodResponseActionSend; + } + + case chap_packet_code_success: + process_success(parse_response(reinterpret_cast(msg), reinterpret_cast(msg_end) - reinterpret_cast(msg))); + if (m_success) { + // Acknowledge the authentication by sending a "3" (chap_packet_code_success). + m_packet_res.assign(1, chap_packet_code_success); + return EapPeerMethodResponseActionSend; + } else + return EapPeerMethodResponseActionDiscard; + + case chap_packet_code_failure: + process_error(parse_response(reinterpret_cast(msg), reinterpret_cast(msg_end) - reinterpret_cast(msg))); + return EapPeerMethodResponseActionDiscard; + } + + pck = msg_end; + } + + return EapPeerMethodResponseActionNone; +} + + +////////////////////////////////////////////////////////////////////// +// eap::method_mschapv2_diameter +////////////////////////////////////////////////////////////////////// + +eap::method_mschapv2_diameter::method_mschapv2_diameter(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred) : + m_phase(phase_unknown), + method_mschapv2_base(mod, cfg, cred) +{ +} + + +eap::method_mschapv2_diameter::method_mschapv2_diameter(_Inout_ method_mschapv2_diameter &&other) : + m_phase (std::move(other.m_phase)), + method_mschapv2_base(std::move(other )) +{ +} + + +eap::method_mschapv2_diameter& eap::method_mschapv2_diameter::operator=(_Inout_ method_mschapv2_diameter &&other) +{ + if (this != std::addressof(other)) { + (method_mschapv2_base&)*this = std::move(other ); + m_phase = std::move(other.m_phase); + } + + return *this; +} + + +void eap::method_mschapv2_diameter::begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize) +{ + method_mschapv2_base::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); m_phase = phase_init; } -EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( +EapPeerMethodResponseAction eap::method_mschapv2_diameter::process_request_packet( _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, _In_ DWORD dwReceivedPacketSize) { @@ -121,7 +389,7 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( 1 + // Flags sizeof(challenge_mschapv2) + // Peer-Challenge 8 + // Reserved - sizeof(nt_response)); // Response + sizeof(nt_response)); // NT-Response response.push_back(m_ident); response.push_back(0); // Flags response.insert(response.end(), reinterpret_cast(&m_challenge_client), reinterpret_cast(&m_challenge_client + 1)); // Peer-Challenge @@ -142,8 +410,6 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( case phase_challenge_server: { process_packet(pReceivedPacket, dwReceivedPacketSize); if (m_success) { - m_module.log_event(&EAPMETHOD_METHOD_SUCCESS, event_data((unsigned int)eap_type_legacy_mschapv2), event_data::blank); - m_phase = phase_finished; // Acknowledge the authentication by sending an empty response packet. @@ -162,36 +428,7 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( } -void eap::method_mschapv2::get_response_packet( - _Out_ sanitizing_blob &packet, - _In_opt_ DWORD size_max) -{ - if (m_packet_res.size() > size_max) - throw invalid_argument(string_printf(__FUNCTION__ " This method does not support packet fragmentation, but the data size is too big to fit in one packet (packet: %u, maximum: %u).", m_packet_res.size(), size_max).c_str()); - - packet.assign(m_packet_res.begin(), m_packet_res.end()); -} - - -void eap::method_mschapv2::get_result( - _In_ EapPeerMethodResultReason reason, - _Inout_ EapPeerMethodResult *pResult) -{ - assert(pResult); - - method::get_result(reason, pResult); - - if (reason == EapPeerMethodResultSuccess) - m_cfg.m_last_status = config_method::status_success; - - // Always ask EAP host to save the connection data. And it will save it *only* when we report "success". - // Don't worry. EapHost is well aware of failed authentication condition. - pResult->fSaveConnectionData = TRUE; - pResult->fIsSuccess = TRUE; -} - - -void eap::method_mschapv2::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) +void eap::method_mschapv2_diameter::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) { for (const unsigned char *pck = reinterpret_cast(_pck), *pck_end = pck + size_pck; pck < pck_end; ) { if (pck + sizeof(diameter_avp_header) > pck_end) @@ -234,90 +471,3 @@ void eap::method_mschapv2::process_packet(_In_bytecount_(size_pck) const void *_ pck = msg_next; } } - - -void eap::method_mschapv2::process_success(_In_ const list &argv) -{ - m_success = false; - - for (auto arg = argv.cbegin(), arg_end = argv.cend(); arg != arg_end; ++arg) { - const string &val = *arg; - if ((val[0] == 'S' || val[0] == 's') && val[1] == '=') { - // "S=" - hex_dec dec; - sanitizing_blob resp; - bool is_last; - dec.decode(resp, is_last, val.data() + 2, (size_t)-1); - - // Calculate expected authenticator response. - sanitizing_string identity_utf8; - WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); - authenticator_response resp_exp(m_cp, m_challenge_server, m_challenge_client, identity_utf8.c_str(), m_cred.m_password.c_str(), m_nt_resp); - - // Compare against provided authemticator response. - if (resp.size() != sizeof(resp_exp) || memcmp(resp.data(), &resp_exp, sizeof(resp_exp)) != 0) - throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string failed."); - - m_success = true; - } - } - - if (!m_success) - throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string not found."); -} - - -void eap::method_mschapv2::process_error(_In_ const list &argv) -{ - for (auto arg = argv.cbegin(), arg_end = argv.cend(); arg != arg_end; ++arg) { - const string &val = *arg; - if ((val[0] == 'E' || val[0] == 'e') && val[1] == '=') { - DWORD dwResult = strtoul(val.data() + 2, NULL, 10); - m_module.log_event(&EAPMETHOD_METHOD_FAILURE_ERROR, event_data((unsigned int)eap_type_legacy_mschapv2), event_data(dwResult), event_data::blank); - switch (dwResult) { - case ERROR_ACCT_DISABLED : m_cfg.m_last_status = config_method::status_account_disabled ; break; - case ERROR_RESTRICTED_LOGON_HOURS: m_cfg.m_last_status = config_method::status_account_logon_hours; break; - case ERROR_NO_DIALIN_PERMISSION : m_cfg.m_last_status = config_method::status_account_denied ; break; - case ERROR_PASSWD_EXPIRED : m_cfg.m_last_status = config_method::status_cred_expired ; break; - case ERROR_CHANGING_PASSWORD : m_cfg.m_last_status = config_method::status_cred_changing ; break; - default : m_cfg.m_last_status = config_method::status_cred_invalid ; - } - } else if ((val[0] == 'C' || val[0] == 'c') && val[1] == '=') { - hex_dec dec; - sanitizing_blob resp; - bool is_last; - dec.decode(resp, is_last, val.data() + 2, (size_t)-1); - if (resp.size() != sizeof(m_challenge_server)) - throw invalid_argument(string_printf(__FUNCTION__ " Incorrect MSCHAPv2 challenge length (expected: %uB, received: %uB).", sizeof(m_challenge_server), resp.size()).c_str()); - memcpy(&m_challenge_server, resp.data(), sizeof(m_challenge_server)); - } else if ((val[0] == 'M' || val[0] == 'm') && val[1] == '=') { - MultiByteToWideChar(CP_UTF8, 0, val.data() + 2, -1, m_cfg.m_last_msg); - m_module.log_event(&EAPMETHOD_METHOD_FAILURE_ERROR1, event_data((unsigned int)eap_type_legacy_mschapv2), event_data(m_cfg.m_last_msg), event_data::blank); - } - } -} - - -list eap::method_mschapv2::parse_response(_In_count_(count) const char *resp, _In_ size_t count) -{ - list argv; - - for (size_t i = 0; i < count && resp[i]; ) { - if (i + 1 < count && (resp[i] == 'M' || resp[i] == 'm') && resp[i + 1] == '=') { - // The message is always the last value. It may contain spaces and it spans to the end. - argv.push_back(string(resp + i, strnlen(resp + i, count - i))); - break; - } else if (!isspace(resp[i])) { - // Search for the next space and add value up to it. - size_t j; - for (j = i + 1; j < count && resp[j] && !isspace(resp[j]); j++); - argv.push_back(string(resp + i, j - i)); - i = j + 1; - } else { - // Skip (multiple) spaces. - i++; - } - } - - return argv; -} diff --git a/lib/TTLS/src/Config.cpp b/lib/TTLS/src/Config.cpp index 3bb1ba9..b56be53 100644 --- a/lib/TTLS/src/Config.cpp +++ b/lib/TTLS/src/Config.cpp @@ -265,10 +265,11 @@ eap::credentials* eap::config_method_ttls::make_credentials() const eap::config_method* eap::config_method_ttls::make_config_method(_In_ winstd::eap_type_t eap_type) const { switch (eap_type) { - case eap_type_legacy_pap : return new config_method_pap (m_module, m_level + 1); - case eap_type_legacy_mschapv2: return new config_method_mschapv2(m_module, m_level + 1); + case eap_type_legacy_pap : return new config_method_pap (m_module, m_level + 1); + case eap_type_legacy_mschapv2: return new config_method_mschapv2 (m_module, m_level + 1); + case eap_type_mschapv2 : return new config_method_eapmschapv2(m_module, m_level + 1); #ifdef EAP_INNER_EAPHOST - default : return new config_method_eaphost (m_module, m_level + 1); // EapHost peer method handles all other method types + default : return new config_method_eaphost (m_module, m_level + 1); // EapHost peer method handles all other method types #endif default : throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); } @@ -277,12 +278,13 @@ eap::config_method* eap::config_method_ttls::make_config_method(_In_ winstd::eap eap::config_method* eap::config_method_ttls::make_config_method(_In_ const wchar_t *eap_type) const { - if (_wcsicmp(eap_type, L"PAP" ) == 0) return new config_method_pap (m_module, m_level + 1); - else if (_wcsicmp(eap_type, L"MSCHAPv2") == 0) return new config_method_mschapv2(m_module, m_level + 1); + if (_wcsicmp(eap_type, L"PAP" ) == 0) return new config_method_pap (m_module, m_level + 1); + else if (_wcsicmp(eap_type, L"MSCHAPv2" ) == 0) return new config_method_mschapv2 (m_module, m_level + 1); + else if (_wcsicmp(eap_type, L"EAP-MSCHAPv2") == 0) return new config_method_eapmschapv2(m_module, m_level + 1); #ifdef EAP_INNER_EAPHOST - else if (_wcsicmp(eap_type, L"EapHost" ) == 0) return new config_method_eaphost (m_module, m_level + 1); + else if (_wcsicmp(eap_type, L"EapHost" ) == 0) return new config_method_eaphost (m_module, m_level + 1); #endif - else throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); + else throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); } diff --git a/lib/TTLS/src/Method.cpp b/lib/TTLS/src/Method.cpp index 628b782..d725e6c 100644 --- a/lib/TTLS/src/Method.cpp +++ b/lib/TTLS/src/Method.cpp @@ -592,7 +592,7 @@ EapPeerMethodResponseAction eap::method_ttls::process_request_packet( m_phase = phase_finished; - method_mschapv2 *inner_mschapv2 = dynamic_cast(m_inner.get()); + method_mschapv2_diameter *inner_mschapv2 = dynamic_cast(m_inner.get()); if (inner_mschapv2) { // Push keying material to inner MSCHAPv2 method. static const DWORD s_key_id = 0x02; // EAP-TTLSv0 Challenge Data diff --git a/lib/TTLS/src/Module.cpp b/lib/TTLS/src/Module.cpp index 8876889..577c529 100644 --- a/lib/TTLS/src/Module.cpp +++ b/lib/TTLS/src/Module.cpp @@ -237,8 +237,12 @@ EAP_SESSION_HANDLE eap::peer_ttls::begin_session( { // Native inner methods switch (cfg_inner->get_method_id()) { - case eap_type_legacy_pap : meth_inner.reset(new method_pap (*this, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; - case eap_type_legacy_mschapv2: meth_inner.reset(new method_mschapv2(*this, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; + case eap_type_legacy_pap : meth_inner.reset(new method_pap (*this, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; + case eap_type_legacy_mschapv2: meth_inner.reset(new method_mschapv2_diameter(*this, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; + case eap_type_mschapv2 : meth_inner.reset( + new method_eapmsg (*this, cred_inner->get_identity().c_str(), + new method_eap (*this, eap_type_mschapv2, + new method_mschapv2(*this, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))))); break; default: throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); } } diff --git a/lib/TTLS_UI/include/TTLS_UI.h b/lib/TTLS_UI/include/TTLS_UI.h index 96a1b86..5a90e1d 100644 --- a/lib/TTLS_UI/include/TTLS_UI.h +++ b/lib/TTLS_UI/include/TTLS_UI.h @@ -104,10 +104,11 @@ protected: wxChoicebook *m_inner_type; ///< Inner authentication type // Temporary inner method configurations to hold data until applied - eap::config_method_pap m_cfg_pap; ///< PAP configuration - eap::config_method_mschapv2 m_cfg_mschapv2; ///< MSCHAPv2 configuration + eap::config_method_pap m_cfg_pap; ///< PAP configuration + eap::config_method_mschapv2 m_cfg_mschapv2; ///< MSCHAPv2 configuration + eap::config_method_eapmschapv2 m_cfg_eapmschapv2; ///< EAP-MSCHAPv2 configuration #ifdef EAP_INNER_EAPHOST - eap::config_method_eaphost m_cfg_eaphost; ///< Inner EAP configuration + eap::config_method_eaphost m_cfg_eaphost; ///< Inner EAP configuration #endif }; diff --git a/lib/TTLS_UI/src/Module.cpp b/lib/TTLS_UI/src/Module.cpp index a505904..06ef5f3 100644 --- a/lib/TTLS_UI/src/Module.cpp +++ b/lib/TTLS_UI/src/Module.cpp @@ -294,14 +294,12 @@ void eap::peer_ttls_ui::invoke_identity_ui( 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; - const eap::config_method_pap *cfg_inner_pap; - const eap::config_method_mschapv2 *cfg_inner_mschapv2; - if ((cfg_inner_pap = dynamic_cast(cfg_method->m_inner.get())) != NULL) - panel = new wxPAPCredentialsPanel(*cfg_prov, *cfg_inner_pap, *dynamic_cast(cred->m_inner.get()), &dlg, false); - else if ((cfg_inner_mschapv2 = dynamic_cast(cfg_method->m_inner.get())) != NULL) - panel = new wxMSCHAPv2CredentialsPanel(*cfg_prov, *cfg_inner_mschapv2, *dynamic_cast(cred->m_inner.get()), &dlg, false); - else - assert(0); // Unsupported inner authentication method type. + 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; + default : wxLogError("Unsupported inner authentication method."); + } panel->SetRemember(src_inner == eap::credentials::source_storage); dlg.AddContent(panel); diff --git a/lib/TTLS_UI/src/TTLS_UI.cpp b/lib/TTLS_UI/src/TTLS_UI.cpp index 146842e..b44cec2 100644 --- a/lib/TTLS_UI/src/TTLS_UI.cpp +++ b/lib/TTLS_UI/src/TTLS_UI.cpp @@ -100,10 +100,11 @@ void wxTTLSConfigPanel::OnUpdateUI(wxUpdateUIEvent& event) ////////////////////////////////////////////////////////////////////// wxTTLSConfigWindow::wxTTLSConfigWindow(eap::config_provider &prov, eap::config_method &cfg, wxWindow* parent) : - m_cfg_pap (cfg.m_module, cfg.m_level + 1), - m_cfg_mschapv2(cfg.m_module, cfg.m_level + 1), + m_cfg_pap (cfg.m_module, cfg.m_level + 1), + m_cfg_mschapv2 (cfg.m_module, cfg.m_level + 1), + m_cfg_eapmschapv2(cfg.m_module, cfg.m_level + 1), #ifdef EAP_INNER_EAPHOST - m_cfg_eaphost (cfg.m_module, cfg.m_level + 1), + m_cfg_eaphost (cfg.m_module, cfg.m_level + 1), #endif wxEAPConfigWindow(prov, cfg, parent) { @@ -124,6 +125,8 @@ wxTTLSConfigWindow::wxTTLSConfigWindow(eap::config_provider &prov, eap::config_m m_inner_type->AddPage(panel_pap, _("PAP")); wxMSCHAPv2ConfigPanel *panel_mschapv2 = new wxMSCHAPv2ConfigPanel(m_prov, m_cfg_mschapv2, m_inner_type); m_inner_type->AddPage(panel_mschapv2, _("MSCHAPv2")); + wxMSCHAPv2ConfigPanel *panel_eapmschapv2 = new wxMSCHAPv2ConfigPanel(m_prov, m_cfg_eapmschapv2, m_inner_type); + m_inner_type->AddPage(panel_eapmschapv2, _("EAP-MSCHAPv2")); #ifdef EAP_INNER_EAPHOST wxEapHostConfigPanel *panel_eaphost = new wxEapHostConfigPanel(m_prov, m_cfg_eaphost, m_inner_type); m_inner_type->AddPage(panel_eaphost, _("Other EAP methods...")); @@ -190,6 +193,11 @@ bool wxTTLSConfigWindow::TransferDataToWindow() m_inner_type->SetSelection(1); // 1=MSCHAPv2 break; + case winstd::eap_type_mschapv2: + m_cfg_eapmschapv2 = dynamic_cast(*cfg_ttls.m_inner); + m_inner_type->SetSelection(2); // 2=EAP-MSCHAPv2 + break; + default: wxFAIL_MSG(wxT("Unsupported inner authentication method type.")); } @@ -198,7 +206,7 @@ bool wxTTLSConfigWindow::TransferDataToWindow() else { // EapHost inner method m_cfg_eaphost = *cfg_inner_eaphost; - m_inner_type->SetSelection(2); // 2=EapHost + m_inner_type->SetSelection(3); // 3=EapHost } #endif @@ -225,8 +233,12 @@ bool wxTTLSConfigWindow::TransferDataFromWindow() cfg_ttls.m_inner.reset(new eap::config_method_mschapv2(m_cfg_mschapv2)); break; + case 2: // 2=EAP-MSCHAPv2 + cfg_ttls.m_inner.reset(new eap::config_method_eapmschapv2(m_cfg_eapmschapv2)); + break; + #ifdef EAP_INNER_EAPHOST - case 2: // 2=EapHost + case 3: // 3=EapHost cfg_ttls.m_inner.reset(new eap::config_method_eaphost(m_cfg_eaphost)); break; #endif