diff --git a/lib/EAPBase/include/Method.h b/lib/EAPBase/include/Method.h index 428cf52..3c31067 100644 --- a/lib/EAPBase/include/Method.h +++ b/lib/EAPBase/include/Method.h @@ -21,14 +21,23 @@ namespace eap { /// - /// EAP and non-EAP method base class + /// Method base class /// class method; /// - /// Non-EAP method base class + /// Tunnel method base class /// - class method_noneap; + /// This is a base class for all the methods that encapsulate inner methods to provide stacking framework. + /// + class method_tunnel; + + /// + /// EAP tunnel method + /// + /// This method encapsulates inner data in EAP packets. + /// + class method_eap; } #pragma once @@ -55,25 +64,23 @@ namespace eap public: /// - /// Constructs an EAP method + /// Constructs a method /// - /// \param[in] mod EAP module to use for global services - /// \param[in] cfg Method configuration - /// \param[in] cred User credentials + /// \param[in] mod Module to use for global services /// - method(_In_ module &module, _In_ config_method &cfg, _In_ credentials &cred); + method(_In_ module &mod); /// - /// Moves an EAP method + /// Moves a method /// - /// \param[in] other EAP method to move from + /// \param[in] other Method to move from /// method(_Inout_ method &&other); /// - /// Moves an EAP method + /// Moves a method /// - /// \param[in] other EAP method to move from + /// \param[in] other Method to move from /// /// \returns Reference to this object /// @@ -115,8 +122,8 @@ namespace eap /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) /// virtual void get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) = 0; + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD) = 0; /// /// Obtains the result of an authentication session from the EAP method. @@ -176,89 +183,193 @@ namespace eap /// @} public: - module &m_module; ///< EAP module - config_method &m_cfg; ///< Connection configuration - credentials &m_cred; ///< User credentials - std::vector m_eap_attr; ///< EAP attributes + module &m_module; ///< Module for global services }; - class method_noneap : public method + class method_tunnel : public method { - WINSTD_NONCOPYABLE(method_noneap) + WINSTD_NONCOPYABLE(method_tunnel) public: /// - /// Constructs a non-EAP method + /// Constructs a method /// - /// \param[in] mod EAP module to use for global services - /// \param[in] cfg Method configuration - /// \param[in] cred User credentials + /// \param[in] mod Module to use for global services + /// \param[in] inner Inner method /// - method_noneap(_In_ module &module, _In_ config_method &cfg, _In_ credentials &cred); + method_tunnel(_In_ module &mod, _In_ method *inner); /// - /// Moves a non-EAP method + /// Moves a method /// - /// \param[in] other EAP method to move from + /// \param[in] other Method to move from /// - method_noneap(_Inout_ method_noneap &&other); + method_tunnel(_Inout_ method_tunnel &&other); /// - /// Moves a non-EAP method + /// Moves a method /// - /// \param[in] other EAP method to move from + /// \param[in] other Method to move from /// /// \returns Reference to this object /// - method_noneap& operator=(_Inout_ method_noneap &&other); + method_tunnel& operator=(_Inout_ method_tunnel &&other); /// \name Packet processing /// @{ /// - /// Obtains a response packet from the non-EAP method. + /// Starts an EAP authentication session on the peer EapHost using the EAP method. + /// + /// \sa [EapPeerBeginSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363600.aspx) + /// + virtual void begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD); + + /// + /// Ends an EAP authentication session for the EAP method. + /// + /// \sa [EapPeerEndSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363604.aspx) + /// + virtual void end_session(); + + /// + /// Processes a packet received by EapHost from a supplicant. + /// + /// \sa [EapPeerProcessRequestPacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363621.aspx) + /// + virtual EapPeerMethodResponseAction process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize); + + /// + /// Obtains a response packet from the EAP method. /// /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) /// virtual void get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize); + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + /// + /// Obtains the result of an authentication session from the EAP method. + /// + /// \sa [EapPeerGetResult function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363611.aspx) + /// + virtual void get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *pResult); + + /// @} + + /// \name User Interaction + /// @{ + + /// + /// Obtains the user interface context from the EAP method. + /// + /// \note This function is always followed by the `EapPeerInvokeInteractiveUI()` function, which is followed by the `EapPeerSetUIContext()` function. + /// + /// \sa [EapPeerGetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363612.aspx) + /// + virtual void get_ui_context( + _Inout_ BYTE **ppUIContextData, + _Inout_ DWORD *pdwUIContextDataSize); + + /// + /// Provides a user interface context to the EAP method. + /// + /// \note This function is called after the UI has been raised through the `EapPeerGetUIContext()` function. + /// + /// \sa [EapPeerSetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363626.aspx) + /// + virtual EapPeerMethodResponseAction set_ui_context( + _In_count_(dwUIContextDataSize) const BYTE *pUIContextData, + _In_ DWORD dwUIContextDataSize); + + /// @} + + /// \name EAP Response Attributes + /// @{ + + /// + /// Obtains an array of EAP response attributes from the EAP method. + /// + /// \sa [EapPeerGetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363609.aspx) + /// + virtual void get_response_attributes(_Inout_ EapAttributes *pAttribs); + + /// + /// Provides an updated array of EAP response attributes to the EAP method. + /// + /// \sa [EapPeerSetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363625.aspx) + /// + virtual EapPeerMethodResponseAction set_response_attributes(_In_ const EapAttributes *pAttribs); /// @} protected: + std::unique_ptr m_inner; ///< Inner method + }; + + + class method_eap : public method_tunnel + { + WINSTD_NONCOPYABLE(method_eap) + + public: /// - /// Appends Diameter AVP to response packet + /// Constructs a method /// - /// \param[in] code AVP code - /// \param[in] flags AVP flags - /// \param[in] data AVP data (<16777212B) - /// \param[in] size Size of \p data in bytes + /// \param[in] mod Module to use for global services + /// \param[in] eap_method EAP method type + /// \param[in] inner Inner method /// - void append_avp( - _In_ unsigned int code, - _In_ unsigned char flags, - _In_bytecount_(size) const void *data, - _In_ unsigned int size); + method_eap(_In_ module &mod, _In_ winstd::eap_type_t eap_method, _In_ method *inner); /// - /// Appends Diameter AVP to response packet + /// Moves a method /// - /// \param[in] code AVP code - /// \param[in] vendor_id Vendor-ID - /// \param[in] flags AVP flags - /// \param[in] data AVP data (<16777212B) - /// \param[in] size Size of \p data in bytes + /// \param[in] other Method to move from /// - void append_avp( - _In_ unsigned int code, - _In_ unsigned int vendor_id, - _In_ unsigned char flags, - _In_bytecount_(size) const void *data, - _In_ unsigned int size); + method_eap(_Inout_ method_eap &&other); + + /// + /// Moves a method + /// + /// \param[in] other Method to move from + /// + /// \returns Reference to this object + /// + method_eap& operator=(_Inout_ method_eap &&other); + + /// \name Packet processing + /// @{ + + /// + /// Processes a packet received by EapHost from a supplicant. + /// + /// \sa [EapPeerProcessRequestPacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363621.aspx) + /// + virtual EapPeerMethodResponseAction process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize); + + /// + /// Obtains a response packet from the EAP method. + /// + /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) + /// + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); protected: - sanitizing_blob m_packet_res; ///< Response packet + const winstd::eap_type_t m_eap_method; ///< EAP method type + unsigned char m_id; ///< Request packet ID }; } diff --git a/lib/EAPBase/src/Method.cpp b/lib/EAPBase/src/Method.cpp index c2b06de..1eca436 100644 --- a/lib/EAPBase/src/Method.cpp +++ b/lib/EAPBase/src/Method.cpp @@ -28,19 +28,14 @@ using namespace winstd; // eap::method ////////////////////////////////////////////////////////////////////// -eap::method::method(_In_ module &module, _In_ config_method &cfg, _In_ credentials &cred) : - m_module(module), - m_cfg(cfg), - m_cred(cred) +eap::method::method(_In_ module &mod) : + m_module(mod) { } eap::method::method(_Inout_ method &&other) : - m_module ( other.m_module ), - m_cfg ( other.m_cfg ), - m_cred ( other.m_cred ), - m_eap_attr(std::move(other.m_eap_attr)) + m_module(other.m_module) { } @@ -49,9 +44,6 @@ eap::method& eap::method::operator=(_Inout_ method &&other) { if (this != std::addressof(other)) { assert(std::addressof(m_module) == std::addressof(other.m_module)); // Move method within same module only! - assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method with same configuration only! - assert(std::addressof(m_cred ) == std::addressof(other.m_cred )); // Move method with same credentials only! - m_eap_attr = std::move(other.m_eap_attr); } return *this; @@ -68,11 +60,6 @@ void eap::method::begin_session( UNREFERENCED_PARAMETER(pAttributeArray); UNREFERENCED_PARAMETER(hTokenImpersonateUser); UNREFERENCED_PARAMETER(dwMaxSendPacketSize); - - // Presume authentication will fail with generic protocol failure. (Pesimist!!!) - // We will reset once we get get_result(Success) call. - m_cfg.m_last_status = config_method::status_auth_failed; - m_cfg.m_last_msg.clear(); } @@ -85,27 +72,8 @@ void eap::method::get_result( _In_ EapPeerMethodResultReason reason, _Inout_ EapPeerMethodResult *pResult) { - assert(pResult); - - switch (reason) { - case EapPeerMethodResultSuccess: { - m_module.log_event(&EAPMETHOD_METHOD_SUCCESS, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank); - m_cfg.m_last_status = config_method::status_success; - break; - } - - case EapPeerMethodResultFailure: - m_module.log_event(&EAPMETHOD_METHOD_FAILURE_ERROR2, event_data((unsigned int)m_cfg.get_method_id()), event_data((unsigned int)m_cfg.m_last_status), event_data::blank); - break; - - default: - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); - } - - // 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; + UNREFERENCED_PARAMETER(reason); + UNREFERENCED_PARAMETER(pResult); } @@ -154,107 +122,192 @@ EapPeerMethodResponseAction eap::method::set_response_attributes(_In_ const EapA ////////////////////////////////////////////////////////////////////// -// eap::method_noneap +// eap::method_tunnel ////////////////////////////////////////////////////////////////////// -eap::method_noneap::method_noneap(_In_ module &module, _In_ config_method &cfg, _In_ credentials &cred) : method(module, cfg, cred) +eap::method_tunnel::method_tunnel(_In_ module &mod, _In_ method *inner) : + m_inner(inner), + method(mod) { } -eap::method_noneap::method_noneap(_Inout_ method_noneap &&other) : - m_packet_res(std::move(other.m_packet_res)), - method (std::move(other )) +eap::method_tunnel::method_tunnel(_Inout_ method_tunnel &&other) : + m_inner(std::move(other.m_inner)), + method (std::move(other )) { } -eap::method_noneap& eap::method_noneap::operator=(_Inout_ method_noneap &&other) +eap::method_tunnel& eap::method_tunnel::operator=(_Inout_ method_tunnel &&other) { if (this != std::addressof(other)) { - assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only! - (method&)*this = std::move(other ); - m_packet_res = std::move(other.m_packet_res); + (method&)*this = std::move(other ); + m_inner = std::move(other.m_inner); } return *this; } -void eap::method_noneap::get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) +void eap::method_tunnel::begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize) { - assert(pdwSendPacketSize); - assert(pSendPacket); + method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); - size_t size_packet = m_packet_res.size(); - if (size_packet > *pdwSendPacketSize) - 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).", size_packet, *pdwSendPacketSize).c_str()); - - memcpy(pSendPacket, m_packet_res.data(), size_packet); - *pdwSendPacketSize = (DWORD)size_packet; - m_packet_res.clear(); + assert(m_inner); + m_inner->begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); } -void eap::method_noneap::append_avp( - _In_ unsigned int code, - _In_ unsigned char flags, - _In_bytecount_(size) const void *data, - _In_ unsigned int size) +void eap::method_tunnel::end_session() { - unsigned int - padding = (unsigned int)((4 - size) % 4), - size_outer; + assert(m_inner); + m_inner->end_session(); - m_packet_res.reserve( - m_packet_res.size() + - (size_outer = - sizeof(diameter_avp_header) + // Diameter header - size) + // Data - padding); // Data padding - - // Diameter AVP header - diameter_avp_header hdr; - *reinterpret_cast(hdr.code) = htonl(code); - hdr.flags = flags; - hton24(size_outer, hdr.length); - m_packet_res.insert(m_packet_res.end(), reinterpret_cast(&hdr), reinterpret_cast(&hdr + 1)); - - // Data - m_packet_res.insert(m_packet_res.end(), reinterpret_cast(data), reinterpret_cast(data) + size); - m_packet_res.insert(m_packet_res.end(), padding, 0); + method::end_session(); } -void eap::method_noneap::append_avp( - _In_ unsigned int code, - _In_ unsigned int vendor_id, - _In_ unsigned char flags, - _In_bytecount_(size) const void *data, - _In_ unsigned int size) +EapPeerMethodResponseAction eap::method_tunnel::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize) { - unsigned int - padding = (unsigned int)((4 - size) % 4), - size_outer; - - m_packet_res.reserve( - m_packet_res.size() + - (size_outer = - sizeof(diameter_avp_header_ven) + // Diameter header - size) + // Data - padding); // Data padding - - // Diameter AVP header - diameter_avp_header_ven hdr; - *reinterpret_cast(hdr.code) = htonl(code); - hdr.flags = flags | diameter_avp_flag_vendor; - hton24(size_outer, hdr.length); - *reinterpret_cast(hdr.vendor) = htonl(vendor_id); - m_packet_res.insert(m_packet_res.end(), reinterpret_cast(&hdr), reinterpret_cast(&hdr + 1)); - - // Data - m_packet_res.insert(m_packet_res.end(), reinterpret_cast(data), reinterpret_cast(data) + size); - m_packet_res.insert(m_packet_res.end(), padding, 0); + assert(m_inner); + return m_inner->process_request_packet(pReceivedPacket, dwReceivedPacketSize); +} + + +void eap::method_tunnel::get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) +{ + assert(m_inner); + m_inner->get_response_packet(packet, size_max); +} + + +void eap::method_tunnel::get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *pResult) +{ + assert(m_inner); + m_inner->get_result(reason, pResult); +} + + +void eap::method_tunnel::get_ui_context( + _Inout_ BYTE **ppUIContextData, + _Inout_ DWORD *pdwUIContextDataSize) +{ + assert(m_inner); + m_inner->get_ui_context(ppUIContextData, pdwUIContextDataSize); +} + + +EapPeerMethodResponseAction eap::method_tunnel::set_ui_context( + _In_count_(dwUIContextDataSize) const BYTE *pUIContextData, + _In_ DWORD dwUIContextDataSize) +{ + assert(m_inner); + return m_inner->set_ui_context(pUIContextData, dwUIContextDataSize); +} + + +void eap::method_tunnel::get_response_attributes(_Inout_ EapAttributes *pAttribs) +{ + assert(m_inner); + m_inner->get_response_attributes(pAttribs); +} + + +EapPeerMethodResponseAction eap::method_tunnel::set_response_attributes(_In_ const EapAttributes *pAttribs) +{ + assert(m_inner); + return m_inner->set_response_attributes(pAttribs); +} + + +////////////////////////////////////////////////////////////////////// +// eap::method_eap +////////////////////////////////////////////////////////////////////// + +eap::method_eap::method_eap(_In_ module &mod, _In_ eap_type_t eap_method, _In_ method *inner) : + m_eap_method(eap_method), + m_id(0), + method_tunnel(mod, inner) +{ +} + + +eap::method_eap::method_eap(_Inout_ method_eap &&other) : + m_eap_method (std::move(other.m_eap_method)), + m_id (std::move(other.m_id )), + method_tunnel(std::move(other )) +{ +} + + +eap::method_eap& eap::method_eap::operator=(_Inout_ method_eap &&other) +{ + if (this != std::addressof(other)) { + assert(m_eap_method == other.m_eap_method); // Move method within same EAP method type only! + (method_tunnel&)*this = std::move(other ); + m_id = std::move(other.m_id); + } + + return *this; +} + + +EapPeerMethodResponseAction eap::method_eap::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize) +{ + assert(dwReceivedPacketSize >= sizeof(EapPacket)); // Request packet should contain an EAP packet header at least. + auto hdr = reinterpret_cast(pReceivedPacket); + + // Parse EAP header. + if (hdr->Code != EapCodeRequest) + throw invalid_argument(string_printf(__FUNCTION__ " Unknown EAP packet received (expected: %u, received: %u).", EapCodeRequest, (int)hdr->Code)); + DWORD size_packet = ntohs(*reinterpret_cast(hdr->Length)); + if (size_packet > dwReceivedPacketSize) + throw invalid_argument(string_printf(__FUNCTION__ " Incorrect EAP packet length (expected: %uB, received: %uB).", size_packet, dwReceivedPacketSize)); + if (hdr->Data[0] != m_eap_method) + throw invalid_argument(string_printf(__FUNCTION__ " Unsupported EAP method (expected: %u, received: %u).", (int)m_eap_method, (int)hdr->Data[0])); + + // Save request packet ID to make matching response packet in get_response_packet() later. + m_id = hdr->Id; + + // Process the data with underlying method. + return method_tunnel::process_request_packet(hdr->Data + 1, size_packet - sizeof(EapPacket)); +} + + +void eap::method_eap::get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) +{ + assert(size_max >= sizeof(EapPacket)); // We should be able to respond with at least an EAP packet header. + + if (size_max > MAXWORD) size_max = MAXWORD; // EAP packets maximum size is 64kB. + packet.reserve(size_max); // To avoid reallocation when inserting EAP packet header later. + + // Get data from underlying method. + method_tunnel::get_response_packet(packet, size_max - sizeof(EapPacket)); + + // Prepare EAP packet header. + EapPacket hdr; + hdr.Code = (BYTE)EapCodeResponse; + hdr.Id = m_id; + size_t size_packet = packet.size() + sizeof(EapPacket); + assert(size_packet <= MAXWORD); // Packets spanning over 64kB are not supported. + *reinterpret_cast(hdr.Length) = htons((unsigned short)size_packet); + hdr.Data[0] = m_eap_method; + + // Insert EAP packet header before data. + packet.insert(packet.begin(), reinterpret_cast(&hdr), reinterpret_cast(&hdr + 1)); } diff --git a/lib/EapHost/include/Method.h b/lib/EapHost/include/Method.h index ff33e82..575aa55 100644 --- a/lib/EapHost/include/Method.h +++ b/lib/EapHost/include/Method.h @@ -23,6 +23,8 @@ namespace eap /// /// EapHost peer method /// + /// A wrapper class to provide system installed 3rd party EAP methods integration. + /// class method_eaphost; } @@ -49,7 +51,7 @@ namespace eap /// \param[in] cfg Method configuration /// \param[in] cred User credentials /// - method_eaphost(_In_ module &module, _In_ config_method_eaphost &cfg, _In_ credentials_eaphost &cred); + method_eaphost(_In_ module &mod, _In_ config_method_eaphost &cfg, _In_ credentials_eaphost &cred); /// /// Moves an EAP method @@ -103,8 +105,8 @@ namespace eap /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) /// virtual void get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize); + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); /// /// Obtains the result of an authentication session from the EAP method. @@ -167,7 +169,7 @@ namespace eap /// /// Converts EapHost peer action to output structure. /// - /// \param[in] action EapHost peer action + /// \param[in] action EapHost peer action /// /// \returns EAP method output action /// @@ -186,9 +188,9 @@ namespace eap } protected: - EAP_SESSIONID m_session_id; ///< EAP session ID + config_method_eaphost &m_cfg; ///< Method configuration + credentials_eaphost &m_cred; ///< Method user credentials - sanitizing_blob m_ctx_req_blob; ///< Inner UI context request - sanitizing_blob m_ctx_res_blob; ///< Inner UI context response + EAP_SESSIONID m_session_id; ///< EAP session ID }; } diff --git a/lib/EapHost/src/Method.cpp b/lib/EapHost/src/Method.cpp index dcb7d43..8e816dc 100644 --- a/lib/EapHost/src/Method.cpp +++ b/lib/EapHost/src/Method.cpp @@ -28,16 +28,20 @@ using namespace winstd; // eap::method_eaphost ////////////////////////////////////////////////////////////////////// -eap::method_eaphost::method_eaphost(_In_ module &module, _In_ config_method_eaphost &cfg, _In_ credentials_eaphost &cred) : +eap::method_eaphost::method_eaphost(_In_ module &mod, _In_ config_method_eaphost &cfg, _In_ credentials_eaphost &cred) : + m_cfg(cfg), + m_cred(cred), m_session_id(0), - method(module, cfg, cred) + method(mod) { } eap::method_eaphost::method_eaphost(_Inout_ method_eaphost &&other) : - m_session_id (std::move(other.m_session_id)), - method(std::move(other )) + m_cfg ( other.m_cfg ), + m_cred ( other.m_cred ), + m_session_id(std::move(other.m_session_id)), + method (std::move(other )) { } @@ -45,8 +49,10 @@ eap::method_eaphost::method_eaphost(_Inout_ method_eaphost &&other) : eap::method_eaphost& eap::method_eaphost::operator=(_Inout_ method_eaphost &&other) { if (this != std::addressof(other)) { + assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method within same configuration only! + assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method within same credentials only! (method&)*this = std::move(other ); - m_session_id = std::move(other.m_session_id); + m_session_id = std::move(other.m_session_id); } return *this; @@ -59,28 +65,30 @@ void eap::method_eaphost::begin_session( _In_ HANDLE hTokenImpersonateUser, _In_opt_ DWORD dwMaxSendPacketSize) { + method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + + // Presume authentication will fail with generic protocol failure. (Pesimist!!!) + // We will reset once we get get_result(Success) call. + m_cfg.m_last_status = config_method::status_auth_failed; + m_cfg.m_last_msg.clear(); + // Create EapHost peer session using available connection data (m_cfg) and user data (m_cred). - auto &cfg = dynamic_cast(m_cfg); - auto &cred = dynamic_cast(m_cred); eap_error_runtime error; DWORD dwResult = EapHostPeerBeginSession( dwFlags, - cfg.get_type(), + m_cfg.get_type(), pAttributeArray, hTokenImpersonateUser, - (DWORD)cfg.m_cfg_blob.size(), - cfg.m_cfg_blob.data(), - (DWORD)cred.m_cred_blob.size(), - cred.m_cred_blob.data(), + (DWORD)m_cfg.m_cfg_blob.size(), + m_cfg.m_cfg_blob.data(), + (DWORD)m_cred.m_cred_blob.size(), + m_cred.m_cred_blob.data(), dwMaxSendPacketSize, NULL, NULL, NULL, &m_session_id, &error._Myptr); if (dwResult == ERROR_SUCCESS) { // Session succesfully created. - method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); - - m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank); } else if (error) throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerBeginSession failed."); else @@ -90,8 +98,6 @@ void eap::method_eaphost::begin_session( void eap::method_eaphost::end_session() { - method::end_session(); - // End EapHost peer session. eap_error_runtime error; DWORD dwResult = EapHostPeerEndSession(m_session_id, &error._Myptr); @@ -101,6 +107,8 @@ void eap::method_eaphost::end_session() throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerEndSession failed."); else throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerEndSession failed."); + + method::end_session(); } @@ -110,8 +118,6 @@ EapPeerMethodResponseAction eap::method_eaphost::process_request_packet( { assert(pReceivedPacket || dwReceivedPacketSize == 0); - m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)m_cfg.get_method_id()), event_data((unsigned int)dwReceivedPacketSize), event_data::blank); - // Let EapHost peer process the packet. EapHostPeerResponseAction action; eap_error_runtime error; @@ -132,24 +138,20 @@ EapPeerMethodResponseAction eap::method_eaphost::process_request_packet( void eap::method_eaphost::get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) { - assert(pdwSendPacketSize); - assert(pSendPacket || !*pdwSendPacketSize); - // Let EapHost peer prepare response packet. - DWORD size_max = *pdwSendPacketSize; - eap_blob_runtime packet; + eap_blob_runtime _packet; eap_error_runtime error; DWORD dwResult = EapHostPeerGetSendPacket( m_session_id, - pdwSendPacketSize, - &packet._Myptr, + &size_max, + &_packet._Myptr, &error._Myptr); if (dwResult == ERROR_SUCCESS) { // Packet successfuly prepared. - memcpy_s(pSendPacket, size_max, packet.get(), *pdwSendPacketSize); + packet.assign(_packet.get(), _packet.get() + size_max); } else if (error) throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetSendPacket failed."); else @@ -161,34 +163,49 @@ void eap::method_eaphost::get_result( _In_ EapPeerMethodResultReason reason, _Inout_ EapPeerMethodResult *pResult) { - assert(pResult); + // Let EapHost peer return result. + eap_error_runtime error; + EapHostPeerMethodResult result = {}; + DWORD dwResult = EapHostPeerGetResult( + m_session_id, + EapHostPeerMethodResultFromMethod, + &result, + &error._Myptr); + if (dwResult == ERROR_SUCCESS) { + // Result successfuly returned. + method::get_result(reason, pResult); - if (reason == EapPeerMethodResultSuccess) { - // Let EapHost peer return result. - eap_error_runtime error; - EapHostPeerMethodResult result = {}; - DWORD dwResult = EapHostPeerGetResult( - m_session_id, - EapHostPeerMethodResultFromMethod, - &result, - &error._Myptr); - if (dwResult == ERROR_SUCCESS) { - // Result successfuly returned. - pResult->fIsSuccess = result.fIsSuccess; - pResult->dwFailureReasonCode = result.dwFailureReasonCode; - pResult->pAttribArray = result.pAttribArray; - pResult->pEapError = result.pEapError; + pResult->dwFailureReasonCode = result.dwFailureReasonCode; + pResult->pAttribArray = result.pAttribArray; - if (result.fSaveConnectionData) - dynamic_cast(m_cfg).m_cfg_blob.assign(result.pConnectionData, result.pConnectionData + result.dwSizeofConnectionData); + if (result.pEapError) { + // Transfer error to our module memory space. + pResult->pEapError = m_module.make_error(result.pEapError); + EapHostPeerFreeEapError(result.pEapError); + result.pEapError = NULL; + } - if (result.fSaveUserData) - dynamic_cast(m_cred).m_cred_blob.assign(result.pUserData, result.pUserData + result.dwSizeofUserData); - } else if (error) - throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetResult failed."); - else - throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetResult failed."); - } + if (result.fSaveConnectionData) { + // Update configuration BLOB. + m_cfg.m_cfg_blob.assign(result.pConnectionData, result.pConnectionData + result.dwSizeofConnectionData); + } + + if (result.fSaveUserData) { + // Update credentials BLOB. + m_cred.m_cred_blob.assign(result.pUserData, result.pUserData + result.dwSizeofUserData); + } + + 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; + } else if (error) + throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetResult failed."); + else + throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetResult failed."); } diff --git a/lib/MSCHAPv2/include/Method.h b/lib/MSCHAPv2/include/Method.h index 8dc25ad..6712c34 100644 --- a/lib/MSCHAPv2/include/Method.h +++ b/lib/MSCHAPv2/include/Method.h @@ -38,7 +38,7 @@ namespace eap namespace eap { - class method_mschapv2 : public method_noneap + class method_mschapv2 : public method { WINSTD_NONCOPYABLE(method_mschapv2) @@ -50,7 +50,7 @@ namespace eap /// \param[in] cfg Method configuration /// \param[in] cred User credentials /// - method_mschapv2(_In_ module &module, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred); + method_mschapv2(_In_ module &mod, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred); /// /// Moves a MSCHAPv2 method @@ -91,6 +91,24 @@ namespace eap _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, _In_ DWORD dwReceivedPacketSize); + /// + /// Obtains a response packet from the EAP method. + /// + /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) + /// + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + /// + /// Obtains the result of an authentication session from the EAP method. + /// + /// \sa [EapPeerGetResult function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363611.aspx) + /// + virtual void get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *pResult); + /// @} friend class method_ttls; // Setting of initial challenge derived from TLS PRF @@ -133,6 +151,7 @@ namespace eap 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 @@ -148,5 +167,7 @@ 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/Method.cpp b/lib/MSCHAPv2/src/Method.cpp index 3f2518a..6fa94f4 100644 --- a/lib/MSCHAPv2/src/Method.cpp +++ b/lib/MSCHAPv2/src/Method.cpp @@ -28,17 +28,19 @@ using namespace winstd; // eap::method_mschapv2 ////////////////////////////////////////////////////////////////////// -eap::method_mschapv2::method_mschapv2(_In_ module &module, _In_ config_method_mschapv2 &cfg, _In_ credentials_pass &cred) : +eap::method_mschapv2::method_mschapv2(_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_noneap(module, cfg, cred) + method(mod) { } eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : + m_cfg ( other.m_cfg ), m_cred ( other.m_cred ), m_cp (std::move(other.m_cp )), m_challenge_server(std::move(other.m_challenge_server)), @@ -47,7 +49,8 @@ eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : m_nt_resp (std::move(other.m_nt_resp )), m_success (std::move(other.m_success )), m_phase (std::move(other.m_phase )), - method_noneap (std::move(other )) + m_packet_res (std::move(other.m_packet_res )), + method (std::move(other )) { } @@ -55,15 +58,17 @@ eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 &&other) { if (this != std::addressof(other)) { - assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only! - (method_noneap&)*this = std::move(other ); - m_cp = std::move(other.m_cp ); - m_challenge_server = std::move(other.m_challenge_server); - m_challenge_client = std::move(other.m_challenge_client); - 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 ); + assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method within same configuration only! + assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method within same credentials only! + (method&)*this = std::move(other ); + m_cp = std::move(other.m_cp ); + m_challenge_server = std::move(other.m_challenge_server); + m_challenge_client = std::move(other.m_challenge_client); + 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 ); } return *this; @@ -76,13 +81,17 @@ void eap::method_mschapv2::begin_session( _In_ HANDLE hTokenImpersonateUser, _In_opt_ DWORD dwMaxSendPacketSize) { - method_noneap::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + + // Presume authentication will fail with generic protocol failure. (Pesimist!!!) + // We will reset once we get get_result(Success) call. + m_cfg.m_last_status = config_method::status_auth_failed; + m_cfg.m_last_msg.clear(); // 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."); - m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_legacy_mschapv2), event_data::blank); m_phase = phase_init; } @@ -93,18 +102,16 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( { assert(pReceivedPacket || dwReceivedPacketSize == 0); - m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)eap_type_legacy_mschapv2), event_data((unsigned int)dwReceivedPacketSize), event_data::blank); - switch (m_phase) { case phase_init: { - // Convert username to UTF-8. - 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_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_legacy_mschapv2), event_data::blank); // 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 MS-CHAP2-Response. @@ -122,9 +129,10 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( response.insert(response.end(), reinterpret_cast(&m_nt_resp), reinterpret_cast(&m_nt_resp + 1)); // NT-Response // Diameter AVP (User-Name=1, MS-CHAP-Challenge=11/311, MS-CHAP2-Response=25/311) - append_avp( 1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size() ); - append_avp(11, 311, diameter_avp_flag_mandatory, reinterpret_cast(&m_challenge_server) , (unsigned int)sizeof(m_challenge_server)); - append_avp(25, 311, diameter_avp_flag_mandatory, response.data() , (unsigned int)response.size() ); + m_packet_res.clear(); + diameter_avp_append( 1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size() , m_packet_res); + diameter_avp_append(11, 311, diameter_avp_flag_mandatory, reinterpret_cast(&m_challenge_server) , (unsigned int)sizeof(m_challenge_server), m_packet_res); + diameter_avp_append(25, 311, diameter_avp_flag_mandatory, response.data() , (unsigned int)response.size() , m_packet_res); m_phase = phase_challenge_server; m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point. @@ -133,9 +141,16 @@ EapPeerMethodResponseAction eap::method_mschapv2::process_request_packet( case phase_challenge_server: { process_packet(pReceivedPacket, dwReceivedPacketSize); - if (m_success) + 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; - return EapPeerMethodResponseActionNone; + + // Acknowledge the authentication by sending an empty response packet. + m_packet_res.clear(); + return EapPeerMethodResponseActionSend; + } else + return EapPeerMethodResponseActionDiscard; } case phase_finished: @@ -147,6 +162,35 @@ 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) { for (const unsigned char *pck = reinterpret_cast(_pck), *pck_end = pck + size_pck; pck < pck_end; ) { diff --git a/lib/PAP/include/Method.h b/lib/PAP/include/Method.h index ff77fc3..32264c4 100644 --- a/lib/PAP/include/Method.h +++ b/lib/PAP/include/Method.h @@ -36,7 +36,7 @@ namespace eap namespace eap { - class method_pap : public method_noneap + class method_pap : public method { WINSTD_NONCOPYABLE(method_pap) @@ -48,7 +48,7 @@ namespace eap /// \param[in] cfg Method configuration /// \param[in] cred User credentials /// - method_pap(_In_ module &module, _In_ config_method_pap &cfg, _In_ credentials_pass &cred); + method_pap(_In_ module &mod, _In_ config_method_pap &cfg, _In_ credentials_pass &cred); /// /// Moves a PAP method @@ -89,15 +89,36 @@ namespace eap _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, _In_ DWORD dwReceivedPacketSize); + /// + /// Obtains a response packet from the EAP method. + /// + /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) + /// + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + /// + /// Obtains the result of an authentication session from the EAP method. + /// + /// \sa [EapPeerGetResult function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363611.aspx) + /// + virtual void get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *pResult); + /// @} protected: - credentials_pass &m_cred; ///< Method user credentials + config_method_pap &m_cfg; ///< Method configuration + credentials_pass &m_cred; ///< Method user credentials enum { - phase_unknown = -1, ///< Unknown phase - phase_init = 0, ///< Handshake initialize - phase_finished, ///< Connection shut down - } m_phase; ///< What phase is our communication at? + phase_unknown = -1, ///< Unknown phase + phase_init = 0, ///< Handshake initialize + phase_finished, ///< Connection shut down + } m_phase; ///< What phase is our communication at? + + sanitizing_blob m_packet_res; ///< Response packet }; } diff --git a/lib/PAP/src/Method.cpp b/lib/PAP/src/Method.cpp index 37ba66a..7d49bb6 100644 --- a/lib/PAP/src/Method.cpp +++ b/lib/PAP/src/Method.cpp @@ -28,18 +28,21 @@ using namespace winstd; // eap::method_pap ////////////////////////////////////////////////////////////////////// -eap::method_pap::method_pap(_In_ module &module, _In_ config_method_pap &cfg, _In_ credentials_pass &cred) : +eap::method_pap::method_pap(_In_ module &mod, _In_ config_method_pap &cfg, _In_ credentials_pass &cred) : + m_cfg(cfg), m_cred(cred), m_phase(phase_unknown), - method_noneap(module, cfg, cred) + method(mod) { } eap::method_pap::method_pap(_Inout_ method_pap &&other) : - m_cred ( other.m_cred ), - m_phase (std::move(other.m_phase )), - method_noneap(std::move(other )) + m_cfg ( other.m_cfg ), + m_cred ( other.m_cred ), + m_phase (std::move(other.m_phase )), + m_packet_res(std::move(other.m_packet_res)), + method (std::move(other )) { } @@ -47,9 +50,11 @@ eap::method_pap::method_pap(_Inout_ method_pap &&other) : eap::method_pap& eap::method_pap::operator=(_Inout_ method_pap &&other) { if (this != std::addressof(other)) { - assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only! - (method_noneap&)*this = std::move(other ); - m_phase = std::move(other.m_phase ); + assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method within same configuration only! + assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method within same credentials only! + (method&)*this = std::move(other ); + m_phase = std::move(other.m_phase ); + m_packet_res = std::move(other.m_packet_res); } return *this; @@ -62,9 +67,13 @@ void eap::method_pap::begin_session( _In_ HANDLE hTokenImpersonateUser, _In_opt_ DWORD dwMaxSendPacketSize) { - method_noneap::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + + // Presume authentication will fail with generic protocol failure. (Pesimist!!!) + // We will reset once we get get_result(Success) call. + m_cfg.m_last_status = config_method::status_auth_failed; + m_cfg.m_last_msg.clear(); - m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_legacy_pap), event_data::blank); m_phase = phase_init; } @@ -74,12 +83,12 @@ EapPeerMethodResponseAction eap::method_pap::process_request_packet( _In_ DWORD dwReceivedPacketSize) { UNREFERENCED_PARAMETER(pReceivedPacket); - assert(pReceivedPacket || dwReceivedPacketSize == 0); - - m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)eap_type_legacy_pap), event_data((unsigned int)dwReceivedPacketSize), event_data::blank); + UNREFERENCED_PARAMETER(dwReceivedPacketSize); switch (m_phase) { case phase_init: { + m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_legacy_pap), event_data::blank); + // Convert username and password to UTF-8. sanitizing_string identity_utf8, password_utf8; WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); @@ -90,8 +99,9 @@ EapPeerMethodResponseAction eap::method_pap::process_request_packet( password_utf8.append(padding_password_ex, 0); // Diameter AVP (User-Name=1, User-Password=2) - append_avp(1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size()); - append_avp(2, diameter_avp_flag_mandatory, password_utf8.data(), (unsigned int)password_utf8.size()); + m_packet_res.clear(); + diameter_avp_append(1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size(), m_packet_res); + diameter_avp_append(2, diameter_avp_flag_mandatory, password_utf8.data(), (unsigned int)password_utf8.size(), m_packet_res); m_phase = phase_finished; m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point. @@ -105,3 +115,32 @@ EapPeerMethodResponseAction eap::method_pap::process_request_packet( throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase).c_str()); } } + + +void eap::method_pap::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_pap::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; +} diff --git a/lib/TLS/build/TLS.vcxproj b/lib/TLS/build/TLS.vcxproj index 626ff0b..5056f7e 100644 --- a/lib/TLS/build/TLS.vcxproj +++ b/lib/TLS/build/TLS.vcxproj @@ -81,13 +81,11 @@ - - Create Create diff --git a/lib/TLS/build/TLS.vcxproj.filters b/lib/TLS/build/TLS.vcxproj.filters index 7982ecc..553f9db 100644 --- a/lib/TLS/build/TLS.vcxproj.filters +++ b/lib/TLS/build/TLS.vcxproj.filters @@ -20,9 +20,6 @@ Header Files - - Header Files - Header Files @@ -37,9 +34,6 @@ Source Files - - Source Files - Source Files diff --git a/lib/TLS/include/Config.h b/lib/TLS/include/Config.h index 4d95a0f..ec05344 100644 --- a/lib/TLS/include/Config.h +++ b/lib/TLS/include/Config.h @@ -45,7 +45,6 @@ namespace eap #pragma once #include "Credentials.h" -#include "Method.h" #include "TLS.h" #include "../../EAPBase/include/Config.h" @@ -183,11 +182,5 @@ namespace eap public: std::list m_trusted_root_ca; ///< Trusted root CAs std::list m_server_names; ///< Acceptable authenticating server names - -#if EAP_TLS < EAP_TLS_SCHANNEL - // Following members are used for session resumptions. They are not exported/imported to XML. - sanitizing_blob m_session_id; ///< TLS session ID - tls_master_secret m_master_secret; ///< TLS master secret -#endif }; } diff --git a/lib/TLS/include/Method.h b/lib/TLS/include/Method.h deleted file mode 100644 index aa30d48..0000000 --- a/lib/TLS/include/Method.h +++ /dev/null @@ -1,490 +0,0 @@ -/* - Copyright 2015-2016 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 . -*/ - -namespace eap -{ - /// - /// EAP-TLS method - /// - class method_tls; -} - - -#pragma once - -#include "Config.h" -#include "Credentials.h" -#include "TLS.h" - -#include "../../EAPBase/include/Method.h" - -#include -#include - -#include -#include - - -namespace eap -{ - class method_tls : public method - { - WINSTD_NONCOPYABLE(method_tls) - - public: -#pragma pack(push) -#pragma pack(1) - /// - /// TLS message - /// - struct message_header - { - tls_message_type_t type; ///< Message type (one of `message_type_t` constants) - tls_version version; ///< SSL/TLS version - unsigned char length[2]; ///< Message length (in network byte order) - }; -#pragma pack(pop) - - public: - /// - /// Constructs an EAP-TLS method - /// - /// \param[in] mod EAP module to use for global services - /// \param[in] cfg Method configuration - /// \param[in] cred User credentials - /// - method_tls(_In_ module &module, _In_ config_method_tls &cfg, _In_ credentials_tls &cred); - - /// - /// Moves an EAP-TLS method - /// - /// \param[in] other EAP-TLS method to move from - /// - method_tls(_Inout_ method_tls &&other); - - /// - /// Moves an EAP-TLS method - /// - /// \param[in] other EAP-TLS method to move from - /// - /// \returns Reference to this object - /// - method_tls& operator=(_Inout_ method_tls &&other); - - /// \name Packet processing - /// @{ - - /// - /// Starts an EAP authentication session on the peer EapHost using the EAP method. - /// - /// \sa [EapPeerBeginSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363600.aspx) - /// - virtual void begin_session( - _In_ DWORD dwFlags, - _In_ const EapAttributes *pAttributeArray, - _In_ HANDLE hTokenImpersonateUser, - _In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD); - - /// - /// Processes a packet received by EapHost from a supplicant. - /// - /// \sa [EapPeerProcessRequestPacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363621.aspx) - /// - virtual EapPeerMethodResponseAction process_request_packet( - _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, - _In_ DWORD dwReceivedPacketSize); - - /// - /// Obtains a response packet from the EAP method. - /// - /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) - /// - virtual void get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize); - - /// - /// Obtains the result of an authentication session from the EAP method. - /// - /// \sa [EapPeerGetResult function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363611.aspx) - /// - virtual void get_result( - _In_ EapPeerMethodResultReason reason, - _Inout_ EapPeerMethodResult *pResult); - - /// @} - - protected: -#if EAP_TLS < EAP_TLS_SCHANNEL - /// \name Client handshake message generation - /// @{ - - /// - /// Makes a TLS client hello message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4.1.2. Client Hello)](https://tools.ietf.org/html/rfc5246#section-7.4.1.2) - /// - /// \returns Client hello message - /// - sanitizing_blob make_client_hello(); - - /// - /// Makes a TLS client certificate message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4.6. Client Certificate)](https://tools.ietf.org/html/rfc5246#section-7.4.6) - /// - /// \returns Client certificate message - /// - sanitizing_blob make_client_cert() const; - - /// - /// Makes a TLS client key exchange message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4.7. Client Key Exchange Message )](https://tools.ietf.org/html/rfc5246#section-7.4.7) - /// - /// \param[in] pms Pre-master secret - /// - /// \returns Client key exchange message - /// - sanitizing_blob make_client_key_exchange(_In_ const tls_master_secret &pms) const; - - /// - /// Makes a TLS finished message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter A.1. Record Layer)](https://tools.ietf.org/html/rfc5246#appendix-A.1) - /// - /// \returns Change cipher spec - /// - eap::sanitizing_blob make_finished() const; - - /// @} - - /// \name Client/Server handshake hashing - /// @{ - - /// - /// Hashes handshake message for "finished" message validation. - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4.9. Finished)](https://tools.ietf.org/html/rfc5246#section-7.4.9) - /// - /// \param[in] data Data to hash - /// \param[in] size \p data size in bytes - /// - inline void hash_handshake(_In_count_(size) const void *data, _In_ size_t size) - { - CryptHashData(m_hash_handshake_msgs_md5 , (const BYTE*)data, (DWORD)size, 0); - CryptHashData(m_hash_handshake_msgs_sha1 , (const BYTE*)data, (DWORD)size, 0); - CryptHashData(m_hash_handshake_msgs_sha256, (const BYTE*)data, (DWORD)size, 0); - } - - /// - /// Hashes handshake message for "finished" message validation. - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4.9. Finished)](https://tools.ietf.org/html/rfc5246#section-7.4.9) - /// - /// \param[in] data Data to hash - /// \param[in] size \p data size in bytes - /// - template - inline void hash_handshake(_In_ const std::vector<_Ty, _Ax> &data) - { - hash_handshake(data.data(), data.size() * sizeof(_Ty)); - } - - /// @} - - /// - /// Makes a TLS message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter A.1. Record Layer)](https://tools.ietf.org/html/rfc5246#appendix-A.1) - /// - /// \param[in] type Message type - /// \param[inout] data Message data contents - /// - /// \returns TLS message message - /// - eap::sanitizing_blob make_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &&data); - - /// @} - -#endif - - /// \name Key derivation - /// @{ - - /// - /// Generates master session key - /// - /// \sa [The EAP-TLS Authentication Protocol (Chapter 2.3. Key Hierarchy)](https://tools.ietf.org/html/rfc5216#section-2.3) - /// - virtual void derive_msk(); - - /// - /// Generates keying material for inner authentication - /// - virtual void derive_challenge(); - - /// @} - -#if EAP_TLS < EAP_TLS_SCHANNEL - - /// \name Server message processing - /// @{ - - /// - /// Processes messages in a TLS packet - /// - /// \param[in] pck Packet data - /// \param[in] size_pck \p pck size in bytes - /// - void process_packet(_In_bytecount_(size_pck) const void *pck, _In_ size_t size_pck); - - /// - /// Processes a TLS change_cipher_spec message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.1. Change Cipher Spec Protocol)](https://tools.ietf.org/html/rfc5246#section-7.1) - /// - /// \param[in] msg TLS change_cipher_spec message data - /// \param[in] size_msg TLS change_cipher_spec message data size - /// - virtual void process_change_cipher_spec(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg); - - /// - /// Processes a TLS alert message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.2. Alert Protocol)](https://tools.ietf.org/html/rfc5246#section-7.2) - /// - /// \param[in] msg TLS alert message data - /// \param[in] size_msg TLS alert message data size - /// - virtual void process_alert(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg); - - /// - /// Processes a TLS handshake message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 7.4. Handshake Protocol)](https://tools.ietf.org/html/rfc5246#section-7.4) - /// - /// \param[in] msg TLS handshake message data - /// \param[in] size_msg TLS handshake message data size - /// - virtual void process_handshake(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg); - -#else - /// - /// Process handshake - /// - EapPeerMethodResponseAction process_handshake(); -#endif - - /// - /// Process application data - /// - EapPeerMethodResponseAction process_application_data(); - - /// - /// Processes a TLS application_data message - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 10. Application Data Protocol)](https://tools.ietf.org/html/rfc5246#section-10) - /// - /// \param[in] msg TLS application_data message data - /// \param[in] size_msg TLS application_data message data size - /// - virtual EapPeerMethodResponseAction process_application_data(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg); - - /// @} - -#if EAP_TLS < EAP_TLS_SCHANNEL_FULL - /// - /// Verifies server's certificate if trusted by configuration - /// - void verify_server_trust() const; -#endif - -#if EAP_TLS < EAP_TLS_SCHANNEL - /// \name Encryption - /// @{ - - /// - /// Encrypt TLS message - /// - /// \param[in] type Message type - /// \param[inout] data TLS message to encrypt - /// - void encrypt_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &data); - - /// - /// Decrypt TLS message - /// - /// \param[in] type Original message type for HMAC verification - /// \param[inout] data TLS message to decrypt - /// - void decrypt_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &data); - - /// - /// Returns maximum netto size of a message for a given TLS message size - /// - /// \param[in] size_message Size of the final TLS message - /// - /// \returns Netto size of message data - /// - size_t get_max_message(_In_ size_t size_message) const; - - /// @} - - /// \name Pseudo-random generation - /// @{ - - /// - /// Calculates pseudo-random P_hash data defined in RFC 5246 - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.1 (Chapter 5. HMAC and the Pseudorandom Function)](https://tools.ietf.org/html/rfc4346#section-5) - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 5. HMAC and the Pseudorandom Function)](https://tools.ietf.org/html/rfc5246#section-5) - /// - /// \param[in] cp Handle of the cryptographics provider - /// \param[in] alg Hashing Algorithm to use (CALG_TLS1PRF = combination of MD5 and SHA-1, CALG_SHA_256...) - /// \param[in] secret Hashing secret key - /// \param[in] seed Random seed - /// \param[in] size_seed \p seed size - /// \param[in] size Number of bytes of pseudo-random data required - /// - /// \returns Generated pseudo-random data (\p size bytes) - /// - static sanitizing_blob prf( - _In_ HCRYPTPROV cp, - _In_ ALG_ID alg, - _In_ const tls_master_secret &secret, - _In_bytecount_(size_seed) const void *seed, - _In_ size_t size_seed, - _In_ size_t size); - - /// - /// Calculates pseudo-random P_hash data defined in RFC 5246 - /// - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.1 (Chapter 5. HMAC and the Pseudorandom Function)](https://tools.ietf.org/html/rfc4346#section-5) - /// \sa [The Transport Layer Security (TLS) Protocol Version 1.2 (Chapter 5. HMAC and the Pseudorandom Function)](https://tools.ietf.org/html/rfc5246#section-5) - /// - /// \param[in] cp Handle of the cryptographics provider - /// \param[in] alg Hashing Algorithm to use (CALG_TLS1PRF = combination of MD5 and SHA-1, CALG_SHA_256...) - /// \param[in] secret Hashing secret key - /// \param[in] seed Random seed - /// \param[in] size Number of bytes of pseudo-random data required - /// - /// \returns Generated pseudo-random data (\p size bytes) - /// - template - inline static sanitizing_blob prf( - _In_ HCRYPTPROV cp, - _In_ ALG_ID alg, - _In_ const tls_master_secret &secret, - _In_ const std::vector<_Ty, _Ax> &seed, - _In_ size_t size) - { - return prf(cp, alg, secret, seed.data(), seed.size() * sizeof(_Ty), size); - } - - /// @} - - /// - /// Creates a key - /// - /// \sa [How to export and import plain text session keys by using CryptoAPI](https://support.microsoft.com/en-us/kb/228786) - /// - /// \param[in] cp Handle of the cryptographics provider - /// \param[in] alg Key algorithm - /// \param[in] key Key that decrypts \p secret - /// \param[in] secret Key data - /// \param[in] size_secret \p secret size - /// - /// \returns Key - /// - HCRYPTKEY create_key( - _In_ HCRYPTPROV cp, - _In_ ALG_ID alg, - _In_ HCRYPTKEY key, - _In_bytecount_(size_secret) const void *secret, - _In_ size_t size_secret); -#endif - - protected: - config_method_tls &m_cfg; ///< EAP-TLS method configuration - credentials_tls &m_cred; ///< EAP-TLS user credentials - HANDLE m_user_ctx; ///< Handle to user context - - packet_tls m_packet_req; ///< Request packet - packet_tls m_packet_res; ///< Response packet - - tls_random m_key_mppe_client; ///< MS-MPPE-Recv-Key - tls_random m_key_mppe_server; ///< MS-MPPE-Send-Key - -#if EAP_TLS < EAP_TLS_SCHANNEL - winstd::crypt_prov m_cp; ///< Cryptography provider for general services - winstd::crypt_prov m_cp_enc_client; ///< Cryptography provider for encryption - winstd::crypt_prov m_cp_enc_server; ///< Cryptography provider for encryption - winstd::crypt_key m_key_exp1; ///< Key for importing derived keys - - tls_version m_tls_version; ///< TLS version in use - ALG_ID m_alg_prf; ///< Pseudo-random function algorithm in use - - tls_conn_state m_state_client; ///< Client TLS connection state - tls_conn_state m_state_client_pending; ///< Client TLS connection state (pending) - tls_conn_state m_state_server; ///< Server TLS connection state - tls_conn_state m_state_server_pending; ///< Server TLS connection state (pending) - - tls_master_secret m_master_secret; ///< TLS master secret - tls_random m_random_client; ///< Client random - tls_random m_random_server; ///< Server random - - sanitizing_blob m_session_id; ///< TLS session ID - bool m_session_resumed; ///< Did TLS session resume? - - std::list m_server_cert_chain; ///< Server certificate chain - - winstd::crypt_hash m_hash_handshake_msgs_md5; ///< Running MD5 hash of handshake messages - winstd::crypt_hash m_hash_handshake_msgs_sha1; ///< Running SHA-1 hash of handshake messages - winstd::crypt_hash m_hash_handshake_msgs_sha256; ///< Running SHA-256 hash of handshake messages - - tls_handshake_flags m_handshake; ///< Handshake flags (map of handshake messages received) - - enum { - phase_unknown = -1, ///< Unknown phase - phase_client_hello = 0, ///< Send client hello - phase_server_hello, ///< Wait for server hello - phase_change_cipher_spec, ///< Wait for change cipher spec - phase_application_data ///< Exchange application data - } m_phase; ///< What phase is our communication at? - - unsigned __int64 m_seq_num_client; ///< Sequence number for encrypting - unsigned __int64 m_seq_num_server; ///< Sequence number for decrypting -#else - winstd::tstring m_sc_target_name; ///< Schannel target name - winstd::sec_credentials m_sc_cred; ///< Schannel client credentials - std::vector m_sc_queue; ///< TLS data queue - winstd::sec_context m_sc_ctx; ///< Schannel context - - enum { - phase_unknown = -1, ///< Unknown phase - phase_handshake_init = 0, ///< Handshake initialize - phase_handshake_cont, ///< Handshake continue - phase_application_data, ///< Exchange application data - phase_shutdown, ///< Connection shut down - } m_phase; ///< What phase is our communication at? -#endif - }; -} diff --git a/lib/TLS/src/Config.cpp b/lib/TLS/src/Config.cpp index a57812d..712ebf7 100644 --- a/lib/TLS/src/Config.cpp +++ b/lib/TLS/src/Config.cpp @@ -75,10 +75,6 @@ eap::config_method_tls::config_method_tls(_In_ module &mod, _In_ unsigned int le eap::config_method_tls::config_method_tls(_In_ const config_method_tls &other) : m_trusted_root_ca(other.m_trusted_root_ca), m_server_names(other.m_server_names), -#if EAP_TLS < EAP_TLS_SCHANNEL - m_session_id(other.m_session_id), - m_master_secret(other.m_master_secret), -#endif config_method_with_cred(other) { } @@ -87,10 +83,6 @@ eap::config_method_tls::config_method_tls(_In_ const config_method_tls &other) : eap::config_method_tls::config_method_tls(_Inout_ config_method_tls &&other) : m_trusted_root_ca(std::move(other.m_trusted_root_ca)), m_server_names(std::move(other.m_server_names)), -#if EAP_TLS < EAP_TLS_SCHANNEL - m_session_id(std::move(other.m_session_id)), - m_master_secret(std::move(other.m_master_secret)), -#endif config_method_with_cred(std::move(other)) { } @@ -102,10 +94,6 @@ eap::config_method_tls& eap::config_method_tls::operator=(_In_ const config_meth (config_method_with_cred&)*this = other; m_trusted_root_ca = other.m_trusted_root_ca; m_server_names = other.m_server_names; -#if EAP_TLS < EAP_TLS_SCHANNEL - m_session_id = other.m_session_id; - m_master_secret = other.m_master_secret; -#endif } return *this; @@ -118,10 +106,6 @@ eap::config_method_tls& eap::config_method_tls::operator=(_Inout_ config_method_ (config_method_with_cred&&)*this = std::move(other); m_trusted_root_ca = std::move(other.m_trusted_root_ca); m_server_names = std::move(other.m_server_names); -#if EAP_TLS < EAP_TLS_SCHANNEL - m_session_id = std::move(other.m_session_id); - m_master_secret = std::move(other.m_master_secret); -#endif } return *this; @@ -250,10 +234,6 @@ void eap::config_method_tls::operator<<(_Inout_ cursor_out &cursor) const config_method_with_cred::operator<<(cursor); cursor << m_trusted_root_ca; cursor << m_server_names ; -#if EAP_TLS < EAP_TLS_SCHANNEL - cursor << m_session_id ; - cursor << m_master_secret ; -#endif } @@ -262,14 +242,7 @@ size_t eap::config_method_tls::get_pk_size() const return config_method_with_cred::get_pk_size() + pksizeof(m_trusted_root_ca) + - pksizeof(m_server_names ) -#if EAP_TLS < EAP_TLS_SCHANNEL - + - pksizeof(m_session_id ) + - pksizeof(m_master_secret ); -#else - ; -#endif + pksizeof(m_server_names ); } @@ -278,10 +251,6 @@ void eap::config_method_tls::operator>>(_Inout_ cursor_in &cursor) config_method_with_cred::operator>>(cursor); cursor >> m_trusted_root_ca; cursor >> m_server_names ; -#if EAP_TLS < EAP_TLS_SCHANNEL - cursor >> m_session_id ; - cursor >> m_master_secret ; -#endif } diff --git a/lib/TLS/src/Method.cpp b/lib/TLS/src/Method.cpp deleted file mode 100644 index 1a2a284..0000000 --- a/lib/TLS/src/Method.cpp +++ /dev/null @@ -1,1855 +0,0 @@ -/* - Copyright 2015-2016 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" - -#if EAP_TLS >= EAP_TLS_SCHANNEL -#pragma comment(lib, "Secur32.lib") -#endif - -using namespace std; -using namespace winstd; - -////////////////////////////////////////////////////////////////////// -// Data -////////////////////////////////////////////////////////////////////// - -#if EAP_TLS < EAP_TLS_SCHANNEL - -static const unsigned char s_cipher_suite[] = { - //0xc0, 0x28, // ECDHE-RSA-AES256-SHA384 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384 - //0xc0, 0x24, // ECDHE-ECDSA-AES256-SHA384 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384 - 0x00, 0x3d, // AES256-SHA256 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA256 - //0x00, 0x6b, // DHE-RSA-AES256-SHA256 Kx=DH Au=RSA Enc=AES(256) Mac=SHA256 - //0x00, 0x6a, // DHE-DSS-AES256-SHA256 Kx=DH Au=DSS Enc=AES(256) Mac=SHA256 - //0xc0, 0x27, // ECDHE-RSA-AES128-SHA256 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256 - //0xc0, 0x23, // ECDHE-ECDSA-AES128-SHA256 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256 - 0x00, 0x3c, // AES128-SHA256 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA256 - //0x00, 0x67, // DHE-RSA-AES128-SHA256 Kx=DH Au=RSA Enc=AES(128) Mac=SHA256 - //0x00, 0x40, // DHE-DSS-AES128-SHA256 Kx=DH Au=DSS Enc=AES(128) Mac=SHA256 - - // Backward compatibility ciphers - 0x00, 0x2f, // TLS_RSA_WITH_AES_128_CBC_SHA (required by TLS 1.2) - 0x00, 0x0a, // TLS_RSA_WITH_3DES_EDE_CBC_SHA (required by EAP-TLS) -}; -static const unsigned char s_compression_suite[] = { - 0x00, // No compression -}; - -#endif - - -////////////////////////////////////////////////////////////////////// -// eap::method_tls -////////////////////////////////////////////////////////////////////// - -eap::method_tls::method_tls(_In_ module &module, _In_ config_method_tls &cfg, _In_ credentials_tls &cred) : - m_cfg(cfg), - m_cred(cred), - m_user_ctx(NULL), -#if EAP_TLS < EAP_TLS_SCHANNEL - m_tls_version(tls_version_1_2), -#ifdef _DEBUG - m_alg_prf(0), -#endif - m_session_resumed(false), - m_phase(phase_unknown), - m_seq_num_client(0), - m_seq_num_server(0), -#else - m_phase(phase_unknown), -#endif - method(module, cfg, cred) -{ -} - - -eap::method_tls::method_tls(_Inout_ method_tls &&other) : - m_cred ( other.m_cred ), - m_cfg ( other.m_cfg ), - m_user_ctx (std::move(other.m_user_ctx )), - m_packet_req (std::move(other.m_packet_req )), - m_packet_res (std::move(other.m_packet_res )), - m_key_mppe_client (std::move(other.m_key_mppe_client )), - m_key_mppe_server (std::move(other.m_key_mppe_server )), -#if EAP_TLS < EAP_TLS_SCHANNEL - m_cp (std::move(other.m_cp )), - m_cp_enc_client (std::move(other.m_cp_enc_client )), - m_cp_enc_server (std::move(other.m_cp_enc_server )), - m_key_exp1 (std::move(other.m_key_exp1 )), - m_tls_version (std::move(other.m_tls_version )), - m_alg_prf (std::move(other.m_alg_prf )), - m_state_client (std::move(other.m_state_client )), - m_state_client_pending (std::move(other.m_state_client_pending )), - m_state_server (std::move(other.m_state_server )), - m_state_server_pending (std::move(other.m_state_server_pending )), - m_master_secret (std::move(other.m_master_secret )), - m_random_client (std::move(other.m_random_client )), - m_random_server (std::move(other.m_random_server )), - m_session_id (std::move(other.m_session_id )), - m_session_resumed (std::move(other.m_session_resumed )), - m_server_cert_chain (std::move(other.m_server_cert_chain )), - m_hash_handshake_msgs_md5 (std::move(other.m_hash_handshake_msgs_md5 )), - m_hash_handshake_msgs_sha1 (std::move(other.m_hash_handshake_msgs_sha1 )), - m_hash_handshake_msgs_sha256(std::move(other.m_hash_handshake_msgs_sha256)), - m_handshake (std::move(other.m_handshake )), - m_phase (std::move(other.m_phase )), - m_seq_num_client (std::move(other.m_seq_num_client )), - m_seq_num_server (std::move(other.m_seq_num_server )), -#else - m_sc_target_name (std::move(other.m_sc_target_name )), - m_sc_cred (std::move(other.m_sc_cred )), - m_sc_queue (std::move(other.m_sc_queue )), - m_sc_ctx (std::move(other.m_sc_ctx )), - m_phase (std::move(other.m_phase )), -#endif - method (std::move(other )) -{ -} - - -eap::method_tls& eap::method_tls::operator=(_Inout_ method_tls &&other) -{ - if (this != std::addressof(other)) { - assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only! - (method&)*this = std::move(other ); - m_user_ctx = std::move(other.m_user_ctx ); - m_packet_req = std::move(other.m_packet_req ); - m_packet_res = std::move(other.m_packet_res ); - m_key_mppe_client = std::move(other.m_key_mppe_client ); - m_key_mppe_server = std::move(other.m_key_mppe_server ); -#if EAP_TLS < EAP_TLS_SCHANNEL - m_cp = std::move(other.m_cp ); - m_cp_enc_client = std::move(other.m_cp_enc_client ); - m_cp_enc_server = std::move(other.m_cp_enc_server ); - m_key_exp1 = std::move(other.m_key_exp1 ); - m_tls_version = std::move(other.m_tls_version ); - m_alg_prf = std::move(other.m_alg_prf ); - m_state_client = std::move(other.m_state_client ); - m_state_client_pending = std::move(other.m_state_client_pending ); - m_state_server = std::move(other.m_state_server ); - m_state_server_pending = std::move(other.m_state_server_pending ); - m_master_secret = std::move(other.m_master_secret ); - m_random_client = std::move(other.m_random_client ); - m_random_server = std::move(other.m_random_server ); - m_session_id = std::move(other.m_session_id ); - m_session_resumed = std::move(other.m_session_resumed ); - m_server_cert_chain = std::move(other.m_server_cert_chain ); - m_hash_handshake_msgs_md5 = std::move(other.m_hash_handshake_msgs_md5 ); - m_hash_handshake_msgs_sha1 = std::move(other.m_hash_handshake_msgs_sha1 ); - m_hash_handshake_msgs_sha256 = std::move(other.m_hash_handshake_msgs_sha256); - m_handshake = std::move(other.m_handshake ); - m_phase = std::move(other.m_phase ); - m_seq_num_client = std::move(other.m_seq_num_client ); - m_seq_num_server = std::move(other.m_seq_num_server ); -#else - m_sc_target_name = std::move(other.m_sc_target_name ); - m_sc_cred = std::move(other.m_sc_cred ); - m_sc_queue = std::move(other.m_sc_queue ); - m_sc_ctx = std::move(other.m_sc_ctx ); - m_phase = std::move(other.m_phase ); -#endif - } - - return *this; -} - - -void eap::method_tls::begin_session( - _In_ DWORD dwFlags, - _In_ const EapAttributes *pAttributeArray, - _In_ HANDLE hTokenImpersonateUser, - _In_opt_ DWORD dwMaxSendPacketSize) -{ - method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); - - m_user_ctx = hTokenImpersonateUser; - user_impersonator impersonating(m_user_ctx); - -#if EAP_TLS < EAP_TLS_SCHANNEL - // Create cryptographics provider for support needs (handshake hashing, client random, temporary keys...). - if (!m_cp.create(NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) - throw win_runtime_error(__FUNCTION__ " Error creating cryptographics provider."); - - // Microsoft CryptoAPI does not support importing clear text session keys. - // Therefore, we trick it to say the session key is "encrypted" with an exponent-of-one key. - if (!m_key_exp1.create_exp1(m_cp, AT_KEYEXCHANGE)) - throw win_runtime_error(__FUNCTION__ " Error creating exponent-of-one key."); - - // Restore previous session ID and master secret. We might get lucky. - m_session_id = m_cfg.m_session_id; - m_master_secret = m_cfg.m_master_secret; -#else - // Build (expected) server name(s) for Schannel. - m_sc_target_name.clear(); - for (auto name = m_cfg.m_server_names.cbegin(), name_end = m_cfg.m_server_names.cend(); name != name_end; ++name) { - if (name != m_cfg.m_server_names.cbegin()) - m_sc_target_name += _T(';'); -#ifdef _UNICODE - m_sc_target_name.insert(m_sc_target_name.end(), name->begin(), name->end()); -#else - string buf; - WideCharToMultiByte(CP_ACP, 0, name->c_str(), -1, buf, NULL, NULL); - m_sc_target_name.insert(m_sc_target_name.end(), buf.begin(), buf.end()); -#endif - } - - // Prepare client credentials for Schannel. - PCCERT_CONTEXT certs[] = { m_cred.m_cert ? (PCCERT_CONTEXT)m_cred.m_cert : NULL }; - SCHANNEL_CRED cred = { - SCHANNEL_CRED_VERSION, // dwVersion - m_cred.m_cert ? 1 : 0, // cCreds - certs, // paCred - NULL, // hRootStore: Not valid for client credentials - 0, // cMappers - NULL, // aphMappers - 0, // cSupportedAlgs: Use system configured default - NULL, // palgSupportedAlgs: Use system configured default - SP_PROT_TLS1_X_CLIENT | (SP_PROT_TLS1_2_CLIENT<<2), // grbitEnabledProtocols: TLS 1.x - 0, // dwMinimumCipherStrength: Use system configured default - 0, // dwMaximumCipherStrength: Use system configured default - 0, // dwSessionLifespan: Use system configured default = 10hr -#if EAP_TLS >= EAP_TLS_SCHANNEL_FULL - SCH_CRED_AUTO_CRED_VALIDATION | // dwFlags: Let Schannel verify server certificate -#else - SCH_CRED_MANUAL_CRED_VALIDATION | // dwFlags: Prevent Schannel verify server certificate (we want to use custom root CA store and multiple name checking) -#endif - SCH_CRED_CACHE_ONLY_URL_RETRIEVAL_ON_CREATE | // dwFlags: Do not attempt online revocation check - we do not expect to have network connection yet - SCH_CRED_IGNORE_NO_REVOCATION_CHECK | // dwFlags: Ignore no-revocation-check errors - as we cannot check for revocation, it makes little sense to insist certificate has to have revocation set-up - SCH_CRED_IGNORE_REVOCATION_OFFLINE | // dwFlags: Ignore offline-revocation errors - we do not expect to have network connection yet - SCH_CRED_NO_DEFAULT_CREDS | // dwFlags: If client certificate we provided is not acceptable, do not try to select one on your own - (m_cfg.m_server_names.empty() ? SCH_CRED_NO_SERVERNAME_CHECK : 0) | // dwFlags: When no expected server name is given, do not do the server name check. - 0x00400000 /*SCH_USE_STRONG_CRYPTO*/, // dwFlags: Do not use broken ciphers - 0 // dwCredFormat - }; - SECURITY_STATUS stat = m_sc_cred.acquire(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred); - if (FAILED(stat)) - throw sec_runtime_error(stat, __FUNCTION__ " Error acquiring Schannel credentials handle."); -#endif -} - - -EapPeerMethodResponseAction eap::method_tls::process_request_packet( - _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, - _In_ DWORD dwReceivedPacketSize) -{ - assert(pReceivedPacket && dwReceivedPacketSize >= 4); - - // Is this a valid EAP-TLS packet? - if (dwReceivedPacketSize < 6) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Packet is too small. EAP-%s packets should be at least 6B."); - // Don't check packet method type, to allow TTLS extension. - - if (!m_packet_req.append_frag((const EapPacket*)pReceivedPacket)) { - // This was not the only/last fragment. Reply with ACK packet. - m_packet_res.m_code = EapCodeResponse; - m_packet_res.m_id = ((const EapPacket*)pReceivedPacket)->Id; - m_packet_res.m_flags = 0; - m_packet_res.m_data.clear(); - return EapPeerMethodResponseActionSend; - } - - if (m_packet_res.m_flags & packet_tls::flags_res_more_frag) { - // We are sending a fragmented message. - if (m_packet_req.is_ack(m_packet_res.m_id)) { - // This is the ACK of our fragmented message packet. Send the next fragment. - m_packet_res.m_id++; - return EapPeerMethodResponseActionSend; - } else - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " ACK expected, received %u-%u-%x.", m_packet_req.m_code, m_packet_req.m_id, m_packet_req.m_flags)); - } - - m_packet_res.m_code = EapCodeResponse; - m_packet_res.m_id = m_packet_req.m_id; - m_packet_res.m_flags = 0; - - EapPeerMethodResponseAction action = EapPeerMethodResponseActionNone; - user_impersonator impersonating(m_user_ctx); - -#if EAP_TLS < EAP_TLS_SCHANNEL - if (((const EapPacket*)pReceivedPacket)->Code == EapCodeRequest && (m_packet_req.m_flags & packet_tls::flags_req_start)) { - // This is the EAP-TLS start message: (re)initialize method. - m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_tls), event_data::blank); - m_phase = phase_client_hello; - } else { - // Process the packet. - m_handshake.clear(); - m_packet_res.m_data.clear(); - process_packet(m_packet_req.m_data.data(), m_packet_req.m_data.size()); - } - - switch (m_phase) { - case phase_application_data: - if (m_handshake[tls_handshake_type_hello_request]) { - // Re-handshake requested. - m_phase = phase_client_hello; - // Do not break out of this case to allow continuing with the following case. - } else - break; - - case phase_client_hello: { - m_tls_version = tls_version_1_2; - - m_key_mppe_client.clear(); - m_key_mppe_server.clear(); - m_server_cert_chain.clear(); - - // Create handshake hashing objects. - if (!m_hash_handshake_msgs_md5.create(m_cp, CALG_MD5)) - throw win_runtime_error(__FUNCTION__ " Error creating MD5 hashing object."); - if (!m_hash_handshake_msgs_sha1.create(m_cp, CALG_SHA1)) - throw win_runtime_error(__FUNCTION__ " Error creating SHA-1 hashing object."); - if (!m_hash_handshake_msgs_sha256.create(m_cp, CALG_SHA_256)) - throw win_runtime_error(__FUNCTION__ " Error creating SHA-256 hashing object."); - - m_seq_num_client = 0; - m_seq_num_server = 0; - - // Build client hello packet. - sanitizing_blob msg_client_hello(make_message(tls_message_type_handshake, make_client_hello())); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), msg_client_hello.begin(), msg_client_hello.end()); - - m_phase = phase_server_hello; - action = EapPeerMethodResponseActionSend; - break; - } - - case phase_server_hello: { - if (!m_handshake[tls_handshake_type_server_hello]) - throw win_runtime_error(__FUNCTION__ " Server did not hello back. No server random! What cipher to use?"); - - // Adopt server state as client pending. - // If server already send the change cipher spec, use active server state. Otherwise pending. - m_state_client_pending = m_state_server.m_alg_encrypt ? m_state_server : m_state_server_pending; - - // Create cryptographics provider. - if (!m_cp_enc_client.create(NULL, m_state_client_pending.m_prov_name, m_state_client_pending.m_prov_type, CRYPT_VERIFYCONTEXT)) - throw win_runtime_error(__FUNCTION__ " Error creating cryptographics provider."); - - if (m_handshake[tls_handshake_type_certificate]) { - // Do we trust this server? - if (m_server_cert_chain.empty()) - throw win_runtime_error(ERROR_ENCRYPTION_FAILED, __FUNCTION__ " Server sent an empty certificate (chain)."); - verify_server_trust(); - } - - if (m_handshake[tls_handshake_type_certificate_request]) { - // Client certificate requested. - sanitizing_blob msg_client_cert(make_message(tls_message_type_handshake, make_client_cert())); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), msg_client_cert.begin(), msg_client_cert.end()); - } - - if (m_handshake[tls_handshake_type_server_hello_done]) { - if (m_server_cert_chain.empty()) - throw win_runtime_error(ERROR_ENCRYPTION_FAILED, __FUNCTION__ " Can not do a client key exchange without a server public key (missing server certificate)."); - - // Generate pre-master secret. PMS will get sanitized in its destructor when going out-of-scope. - // Always use latest supported version by client (not negotiated one, to detect version rollback attacks). - tls_master_secret pms(m_cp, tls_version_1_2); - - // Derive master secret. - static const unsigned char s_label[] = "master secret"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - memcpy(&m_master_secret, prf(m_cp, m_alg_prf, pms, seed, sizeof(tls_master_secret)).data(), sizeof(tls_master_secret)); - - // Create client key exchange message, and append to packet. - sanitizing_blob msg_client_key_exchange(make_message(tls_message_type_handshake, make_client_key_exchange(pms))); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), msg_client_key_exchange.begin(), msg_client_key_exchange.end()); - } - - if (m_handshake[tls_handshake_type_certificate_request]) { - // TODO: Create and append client certificate verify message! - } - - // Append change cipher spec to packet. - sanitizing_blob ccs(make_message(tls_message_type_change_cipher_spec, sanitizing_blob(1, 1))); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), ccs.begin(), ccs.end()); - - // Derive client side keys. - static const unsigned char s_label[] = "key expansion"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - sanitizing_blob key_block(prf(m_cp, m_alg_prf, m_master_secret, seed, - 2*m_state_client_pending.m_size_mac_key + // client_write_MAC_secret & server_write_MAC_secret (SHA1) - 2*m_state_client_pending.m_size_enc_key + // client_write_key & server_write_key - 2*m_state_client_pending.m_size_enc_iv )); // client_write_IV & server_write_IV - const unsigned char *_key_block = key_block.data(); - - // client_write_MAC_secret - m_state_client_pending.m_padding_hmac = hmac_padding(m_cp, m_state_client_pending.m_alg_mac, _key_block, m_state_client_pending.m_size_mac_key); - _key_block += m_state_client_pending.m_size_mac_key; - - // server_write_MAC_secret - _key_block += m_state_client_pending.m_size_mac_key; - - // client_write_key - m_state_client_pending.m_key = create_key(m_cp_enc_client, m_state_client_pending.m_alg_encrypt, m_key_exp1, _key_block, m_state_client_pending.m_size_enc_key); - _key_block += m_state_client_pending.m_size_enc_key; - - // server_write_key - _key_block += m_state_client_pending.m_size_enc_key; - - if (m_state_client_pending.m_size_enc_iv && m_tls_version < tls_version_1_1) { - // client_write_IV - if (!CryptSetKeyParam(m_state_client_pending.m_key, KP_IV, _key_block, 0)) - throw win_runtime_error(__FUNCTION__ " Error setting client_write_IV."); - _key_block += m_state_client_pending.m_size_enc_iv; - } - - // Accept client pending state as current client state. - m_state_client = std::move(m_state_client_pending); - - // Create finished message, and append to packet. - sanitizing_blob msg_finished(make_message(tls_message_type_handshake, make_finished())); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), msg_finished.begin(), msg_finished.end()); - - // Derive challenge for inner authentication (if any). - derive_challenge(); - - if (m_handshake[tls_handshake_type_finished]) { - // Go to application data phase. And allow piggybacking of the first data message. - m_session_resumed = true; - m_phase = phase_application_data; - process_application_data(NULL, 0); - } else { - m_session_resumed = false; - m_phase = phase_change_cipher_spec; - m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point. - } - - action = EapPeerMethodResponseActionSend; - break; - } - - case phase_change_cipher_spec: - // Wait in this phase until server sends change cipher spec and finish. - if (m_state_server.m_alg_encrypt && m_handshake[tls_handshake_type_finished]) { - m_phase = phase_application_data; - process_application_data(NULL, 0); - } - - // Keep replying (with application data or with ACK packet). - action = EapPeerMethodResponseActionSend; - break; - - default: - throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase).c_str()); - } -#else - if (((const EapPacket*)pReceivedPacket)->Code == EapCodeRequest && (m_packet_req.m_flags & packet_tls::flags_req_start)) { - // This is the EAP-TLS start message: (re)initialize method. - m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_tls), event_data::blank); - m_phase = phase_handshake_init; - } else - m_sc_queue.insert(m_sc_queue.end(), m_packet_req.m_data.begin(), m_packet_req.m_data.end()); - - switch (m_phase) { - case phase_handshake_init: - m_key_mppe_client.clear(); - m_key_mppe_server.clear(); - m_sc_queue.assign(m_packet_req.m_data.begin(), m_packet_req.m_data.end()); - // Do not break out of this case to allow continuing with the following case. - - case phase_handshake_cont: - action = process_handshake(); - break; - - case phase_application_data: - action = process_application_data(); - break; - - default: - throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase).c_str()); - } -#endif - - // EAP-Request packet was processed. Clear its data since we use the absence of data to detect first of fragmented message packages. - m_packet_req.m_data.clear(); - return action; -} - - -void eap::method_tls::get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) -{ - assert(pdwSendPacketSize); - assert(pSendPacket); - - *pdwSendPacketSize = m_packet_res.get_frag((EapPacket*)pSendPacket, *pdwSendPacketSize); -} - - -void eap::method_tls::get_result( - _In_ EapPeerMethodResultReason reason, - _Inout_ EapPeerMethodResult *pResult) -{ - assert(pResult); - - method::get_result(reason, pResult); - - switch (reason) { - case EapPeerMethodResultSuccess: { - // Derive MSK/EMSK for line encryption. - derive_msk(); - - // Fill array with RADIUS attributes. - eap_attr a; - m_eap_attr.reserve(m_eap_attr.size() + 3); - a.create_ms_mppe_key(16, reinterpret_cast(&m_key_mppe_client), sizeof(tls_random)); - m_eap_attr.push_back(std::move(a)); - a.create_ms_mppe_key(17, reinterpret_cast(&m_key_mppe_server), sizeof(tls_random)); - m_eap_attr.push_back(std::move(a)); - m_eap_attr.push_back(eap_attr::blank); - -#if EAP_TLS < EAP_TLS_SCHANNEL - // Update configuration with session resumption data. - m_cfg.m_session_id = m_session_id; - m_cfg.m_master_secret = m_master_secret; -#else - // Make a graceful Schannel shutdown... - // ...as a desparate attempt Schannel would resume session next time then?! - DWORD dwType = SCHANNEL_SHUTDOWN; - SecBuffer token = { - sizeof(dwType), - SECBUFFER_TOKEN, - &dwType }; - SecBufferDesc token_desc = { SECBUFFER_VERSION, 1, &token }; - SECURITY_STATUS status; - if (SUCCEEDED(status = ApplyControlToken(m_sc_ctx, &token_desc))) { - // Prepare output buffer(s). - SecBuffer buf_out[] = { { 0, SECBUFFER_TOKEN, NULL }, }; - sec_buffer_desc buf_out_desc(buf_out, _countof(buf_out)); - - // Build an SSL close notify message. - status = m_sc_ctx.process( - m_sc_cred, - !m_sc_target_name.empty() ? m_sc_target_name.c_str() : NULL, - ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | ISC_REQ_STREAM | /*ISC_REQ_USE_SUPPLIED_CREDS |*/ ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY, - 0, - NULL, - &buf_out_desc); - if (SUCCEEDED(status)) - m_packet_res.m_data.insert(m_packet_res.m_data.end(), reinterpret_cast(buf_out[0].pvBuffer), reinterpret_cast(buf_out[0].pvBuffer) + buf_out[0].cbBuffer); - } -#endif - - break; - } - - case EapPeerMethodResultFailure: -#if EAP_TLS < EAP_TLS_SCHANNEL - // Clear session resumption data. - m_cfg.m_session_id.clear(); - m_cfg.m_master_secret.clear(); -#else - // TODO: Research how a Schannel session context can be cleared not to resume. However, until we find a way how to make Schannel resume session in the first place, we can safely ignore this. -#endif - - break; - } -} - - -#if EAP_TLS < EAP_TLS_SCHANNEL - -eap::sanitizing_blob eap::method_tls::make_client_hello() -{ - size_t size_data; - sanitizing_blob msg; - msg.reserve( - 4 + // SSL header - (size_data = - 2 + // SSL version - sizeof(tls_random) + // Client random - 1 + // Session ID size - m_session_id.size() + // Session ID - 2 + // Length of cypher suite list - sizeof(s_cipher_suite) + // Cipher suite list - 1 + // Length of compression suite - sizeof(s_compression_suite))); // Compression suite - - // SSL header - assert(size_data <= 0xffffff); - unsigned int ssl_header = htonl((tls_handshake_type_client_hello << 24) | (unsigned int)size_data); - msg.insert(msg.end(), reinterpret_cast(&ssl_header), reinterpret_cast(&ssl_header + 1)); - - // SSL version - msg.insert(msg.end(), reinterpret_cast(&m_tls_version), reinterpret_cast(&m_tls_version + 1)); - - // Generate client random and add it to the message - m_random_client.randomize(m_cp); - msg.insert(msg.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - - // Session ID - assert(m_session_id.size() <= 32); - msg.push_back((unsigned char)m_session_id.size()); - msg.insert(msg.end(), m_session_id.begin(), m_session_id.end()); - - // Cypher suite list - unsigned short size_cipher_suite2 = htons((unsigned short)sizeof(s_cipher_suite)); - msg.insert(msg.end(), reinterpret_cast(&size_cipher_suite2), reinterpret_cast(&size_cipher_suite2 + 1)); - msg.insert(msg.end(), s_cipher_suite, s_cipher_suite + _countof(s_cipher_suite)); - - // Compression - msg.push_back((unsigned char)sizeof(s_compression_suite)); - msg.insert(msg.end(), s_compression_suite, s_compression_suite + _countof(s_compression_suite)); - - return msg; -} - - -eap::sanitizing_blob eap::method_tls::make_client_cert() const -{ - // Select client certificate. - PCCERT_CONTEXT cert = m_cred.m_cert ? m_cred.m_cert : NULL; - - size_t size_data, size_list; - sanitizing_blob msg; - msg.reserve( - 4 + // SSL header - (size_data = - 3 + // Certificate list size - (size_list = - (cert ? 3 + cert->cbCertEncoded : 0)))); // Certificate (optional) - - // SSL header - assert(size_data <= 0xffffff); - unsigned int ssl_header = htonl((tls_handshake_type_certificate << 24) | (unsigned int)size_data); - msg.insert(msg.end(), reinterpret_cast(&ssl_header), reinterpret_cast(&ssl_header + 1)); - - // List size - assert(size_list <= 0xffffff); - msg.push_back((unsigned char)((size_list >> 16) & 0xff)); - msg.push_back((unsigned char)((size_list >> 8) & 0xff)); - msg.push_back((unsigned char)((size_list ) & 0xff)); - - if (cert) { - // Cert size - assert(cert->cbCertEncoded <= 0xffffff); - msg.push_back((unsigned char)((cert->cbCertEncoded >> 16) & 0xff)); - msg.push_back((unsigned char)((cert->cbCertEncoded >> 8) & 0xff)); - msg.push_back((unsigned char)((cert->cbCertEncoded ) & 0xff)); - - msg.insert(msg.end(), cert->pbCertEncoded, cert->pbCertEncoded + cert->cbCertEncoded); - } - - return msg; -} - - -eap::sanitizing_blob eap::method_tls::make_client_key_exchange(_In_ const tls_master_secret &pms) const -{ - // Encrypt pre-master key with server public key first. - sanitizing_blob pms_enc(reinterpret_cast(&pms), reinterpret_cast(&pms + 1)); - crypt_key key; - if (!key.import_public(m_cp_enc_client, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &(m_server_cert_chain.front()->pCertInfo->SubjectPublicKeyInfo))) - throw win_runtime_error(__FUNCTION__ " Error importing server's public key."); - if (!CryptEncrypt(key, NULL, TRUE, 0, pms_enc)) - throw win_runtime_error(__FUNCTION__ " Error encrypting PMS."); - - size_t size_data, size_pms_enc = pms_enc.size(); - sanitizing_blob msg; - msg.reserve( - 4 + // SSL header - (size_data = - 2 + // Encrypted pre master secret size - size_pms_enc)); // Encrypted pre master secret - - // SSL header - assert(size_data <= 0xffffff); - unsigned int ssl_header = htonl((tls_handshake_type_client_key_exchange << 24) | (unsigned int)size_data); - msg.insert(msg.end(), reinterpret_cast(&ssl_header), reinterpret_cast(&ssl_header + 1)); - - // Encrypted pre master secret size - assert(size_pms_enc <= 0xffff); - msg.push_back((unsigned char)((size_pms_enc >> 8) & 0xff)); - msg.push_back((unsigned char)((size_pms_enc ) & 0xff)); - - // Encrypted pre master secret -#ifdef _HOST_LOW_ENDIAN - std::reverse(pms_enc.begin(), pms_enc.end()); -#endif - msg.insert(msg.end(), pms_enc.begin(), pms_enc.end()); - - return msg; -} - - -eap::sanitizing_blob eap::method_tls::make_finished() const -{ - sanitizing_blob msg; - msg.reserve( - 4 + // SSL header - 12); // verify_data is 12B - - // SSL header - unsigned int ssl_header = htonl((unsigned int)(tls_handshake_type_finished << 24) | 12); - msg.insert(msg.end(), reinterpret_cast(&ssl_header), reinterpret_cast(&ssl_header + 1)); - - // Create label + hash MD5 + hash SHA-1 seed. - crypt_hash hash; - static const unsigned char s_label[] = "client finished"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1), hash_data; - if (m_tls_version < tls_version_1_2) { - hash = m_hash_handshake_msgs_md5; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing MD5 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - hash = m_hash_handshake_msgs_sha1; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing SHA-1 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - } else { - hash = m_hash_handshake_msgs_sha256; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing SHA-256 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - } - sanitizing_blob verify(prf(m_cp, m_alg_prf, m_master_secret, seed, 12)); - msg.insert(msg.end(), verify.begin(), verify.end()); - - return msg; -} - - -eap::sanitizing_blob eap::method_tls::make_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &&data) -{ - if (type == tls_message_type_handshake) - hash_handshake(data); - - if (m_state_client.m_alg_encrypt) - encrypt_message(type, data); - - size_t size_data = data.size(); - assert(size_data <= 0xffff); - message_header hdr = { - type, // SSL record type - { - m_tls_version.major, // SSL major version - m_tls_version.minor, // SSL minor version - }, - { - // Data length (unencrypted, network byte order) - (unsigned char)((size_data >> 8) & 0xff), - (unsigned char)((size_data ) & 0xff), - } - }; - - sanitizing_blob msg; - msg.reserve(sizeof(message_header) + size_data); - msg.assign(reinterpret_cast(&hdr), reinterpret_cast(&hdr + 1)); - msg.insert(msg.end(), data.begin(), data.end()); - return msg; -} - -#endif - -void eap::method_tls::derive_msk() -{ - const unsigned char *_key_block; - -#if EAP_TLS < EAP_TLS_SCHANNEL - static const unsigned char s_label[] = "client EAP encryption"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - sanitizing_blob key_block(prf(m_cp, m_alg_prf, m_master_secret, seed, 2*sizeof(tls_random))); - _key_block = key_block.data(); -#else - // Derive MSK/EMSK for line encryption. - SecPkgContext_EapKeyBlock key_block; - SECURITY_STATUS status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error generating MSK in Schannel."); - _key_block = key_block.rgbKeys; -#endif - - // MS-MPPE-Recv-Key - memcpy(&m_key_mppe_client, _key_block, sizeof(tls_random)); - _key_block += sizeof(tls_random); - - // MS-MPPE-Send-Key - memcpy(&m_key_mppe_server, _key_block, sizeof(tls_random)); - _key_block += sizeof(tls_random); -} - - -void eap::method_tls::derive_challenge() -{ -} - - -#if EAP_TLS < EAP_TLS_SCHANNEL - -void eap::method_tls::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) -{ - sanitizing_blob data; - - for (const unsigned char *pck = reinterpret_cast(_pck), *pck_end = pck + size_pck; pck < pck_end; ) { - if (pck + 5 > pck_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message header."); - const message_header *hdr = (const message_header*)pck; - const unsigned char - *msg = reinterpret_cast(hdr + 1), - *msg_end = msg + ntohs(*reinterpret_cast(hdr->length)); - if (msg_end > pck_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message data."); - - if (hdr->version >= tls_version_1_0) { - // Process TLS message. - switch (hdr->type) { - case tls_message_type_change_cipher_spec: - if (m_state_server.m_alg_encrypt) { - sanitizing_blob msg_dec(msg, msg_end); - decrypt_message(hdr->type, msg_dec); - process_change_cipher_spec(msg_dec.data(), msg_dec.size()); - } else - process_change_cipher_spec(msg, msg_end - msg); - break; - - case tls_message_type_alert: - if (m_state_server.m_alg_encrypt) { - sanitizing_blob msg_dec(msg, msg_end); - decrypt_message(hdr->type, msg_dec); - process_alert(msg_dec.data(), msg_dec.size()); - } else - process_alert(msg, msg_end - msg); - break; - - case tls_message_type_handshake: - if (m_state_server.m_alg_encrypt) { - sanitizing_blob msg_dec(msg, msg_end); - decrypt_message(hdr->type, msg_dec); - process_handshake(msg_dec.data(), msg_dec.size()); - } else - process_handshake(msg, msg_end - msg); - break; - - case tls_message_type_application_data: { - if (!m_state_server.m_alg_encrypt) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Application data should be encrypted."); - - sanitizing_blob msg_dec(msg, msg_end); - decrypt_message(hdr->type, msg_dec); - process_application_data(msg_dec.data(), msg_dec.size()); - break; - } - } - } - - pck = msg_end; - } -} - - -void eap::method_tls::process_change_cipher_spec(_In_bytecount_(size_msg) const void *_msg, _In_ size_t size_msg) -{ - if (size_msg < 1) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete change cipher spec."); - - const unsigned char *msg = reinterpret_cast(_msg); - if (msg[0] != 1) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " Invalid change cipher spec message (expected 1, received %u).", msg[0])); - - m_module.log_event(&EAPMETHOD_TLS_CHANGE_CIPHER_SPEC, event_data((unsigned int)eap_type_tls), event_data::blank); - - if (!m_state_server_pending.m_alg_encrypt) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Change cipher spec received without cipher being negotiated first."); - - // Create cryptographics provider (based on server selected cipher?). - if (!m_cp_enc_server.create(NULL, m_state_server_pending.m_prov_name, m_state_server_pending.m_prov_type)) - throw win_runtime_error(__FUNCTION__ " Error creating cryptographics provider."); - - static const unsigned char s_label[] = "key expansion"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - sanitizing_blob key_block(prf(m_cp, m_alg_prf, m_master_secret, seed, - 2*m_state_server_pending.m_size_mac_key + // client_write_MAC_secret & server_write_MAC_secret (SHA1) - 2*m_state_server_pending.m_size_enc_key + // client_write_key & server_write_key - 2*m_state_server_pending.m_size_enc_iv )); // client_write_IV & server_write_IV - const unsigned char *_key_block = key_block.data(); - - // client_write_MAC_secret - _key_block += m_state_server_pending.m_size_mac_key; - - // server_write_MAC_secret - m_state_server_pending.m_padding_hmac = hmac_padding(m_cp, m_state_server_pending.m_alg_mac, _key_block, m_state_server_pending.m_size_mac_key); - _key_block += m_state_server_pending.m_size_mac_key; - - // client_write_key - _key_block += m_state_server_pending.m_size_enc_key; - - // server_write_key - m_state_server_pending.m_key = create_key(m_cp_enc_server, m_state_server_pending.m_alg_encrypt, m_key_exp1, _key_block, m_state_server_pending.m_size_enc_key); - _key_block += m_state_server_pending.m_size_enc_key; - - if (m_state_server_pending.m_size_enc_iv && m_tls_version < tls_version_1_1) { - // client_write_IV - _key_block += m_state_server_pending.m_size_enc_iv; - - // server_write_IV - if (!CryptSetKeyParam(m_state_server_pending.m_key, KP_IV, _key_block, 0)) - throw win_runtime_error(__FUNCTION__ " Error setting server_write_IV."); - _key_block += m_state_server_pending.m_size_enc_iv; - } - - // Accept server pending state as current server state. - m_state_server = std::move(m_state_server_pending); - m_state_server_pending.m_alg_encrypt = 0; // Explicitly invalidate server pending state. (To mark that server must re-negotiate cipher.) -} - - -void eap::method_tls::process_alert(_In_bytecount_(size_msg) const void *_msg, _In_ size_t size_msg) -{ - if (size_msg < 2) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete alert."); - - const unsigned char *msg = reinterpret_cast(_msg); - - m_module.log_event(&EAPMETHOD_TLS_ALERT, event_data((unsigned int)eap_type_tls), event_data((unsigned char)msg[0]), event_data((unsigned char)msg[1]), event_data::blank); - - //if (msg[0] == alert_level_fatal) { - // // Clear session ID to avoid reconnection attempts. - // m_session_id.clear(); - //} -} - - -void eap::method_tls::process_handshake(_In_bytecount_(size_msg) const void *_msg, _In_ size_t size_msg) -{ - for (const unsigned char *msg = reinterpret_cast(_msg), *msg_end = msg + size_msg; msg < msg_end; ) { - // Parse record header. - if (msg + sizeof(unsigned int) > msg_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete record header."); - unsigned int hdr = ntohl(*reinterpret_cast(msg)); - const unsigned char - *rec = msg + sizeof(unsigned int), - *rec_end = rec + (hdr & 0xffffff); - if (rec_end > msg_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete record data."); - - // Process record. - tls_handshake_type_t type = (tls_handshake_type_t)((hdr >> 24) & 0xff); - switch (type) { - case tls_handshake_type_server_hello: - // TLS version - if (rec + 2 > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Server SSL/TLS version missing or incomplete."); - else if (*reinterpret_cast(rec) < tls_version_1_0 || m_tls_version < *reinterpret_cast(rec)) - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Unsupported SSL/TLS version."); - m_tls_version = *reinterpret_cast(rec); - m_alg_prf = m_tls_version < tls_version_1_2 ? CALG_TLS1PRF : CALG_SHA_256; - rec += 2; - - // Server random - if (rec + sizeof(tls_random) > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Server random missing or incomplete."); - memcpy(&m_random_server, rec, sizeof(tls_random)); - rec += sizeof(tls_random); - - // Session ID - if (rec + 1 > rec_end || rec + 1 + rec[0] > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Session ID missing or incomplete."); - assert(rec[0] <= 32); // According to RFC 5246 session IDs should not be longer than 32B. - if (m_session_id.size() != rec[0] || memcmp(m_session_id.data(), rec + 1, rec[0]) != 0) { - m_module.log_event(&EAPMETHOD_TLS_SESSION_NEW, event_data((unsigned int)eap_type_tls), event_data::blank); - m_session_id.assign(rec + 1, rec + 1 + rec[0]); - } else - m_module.log_event(&EAPMETHOD_TLS_SESSION_RESUME, event_data((unsigned int)eap_type_tls), event_data::blank); - rec += rec[0] + 1; - - // Cipher - if (rec + 2 > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Cipher missing or incomplete."); - - // Verify the server selected one of our ciphers. - for (size_t i = 0; ; i += 2) { - if (i < _countof(s_cipher_suite)) { - if (s_cipher_suite[i] == rec[0] && s_cipher_suite[i + 1] == rec[1]) - break; - } else - throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Other than requested cipher selected (received 0x%02x%02x).", rec[0], rec[1])); - } - m_state_server_pending.set_cipher(rec); - rec += 2; - - // Compression - if (rec + 1 > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Compression missing or incomplete."); - - // Verify the server selected one of our compression schemes. - for (size_t i = 0; ; i++) { - if (i < _countof(s_compression_suite)) { - if (s_compression_suite[i] == rec[0]) - break; - } else - throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Other than requested compression selected (received 0x%02).", rec[0])); - } - rec++; - - m_module.log_event(&EAPMETHOD_TLS_SERVER_HELLO1, - event_data((unsigned int)eap_type_tls), - event_data(((unsigned int)m_tls_version.major << 8) | (unsigned int)m_tls_version.minor), - event_data((unsigned int)m_session_id.size()), - event_data(m_session_id.data(), (ULONG)m_session_id.size()), - event_data::blank); - break; - - case tls_handshake_type_certificate: { - // Certificate list size - if (rec + 3 > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Certificate list size missing or incomplete."); - const unsigned char - *list = rec + 3, - *list_end = list + ((rec[0] << 16) | (rec[1] << 8) | rec[2]); - if (list_end > rec_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Certificate list missing or incomplete."); - - m_server_cert_chain.clear(); - while (list < list_end) { - // Certificate size - if (list + 3 > list_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Certificate size missing or incomplete."); - const unsigned char - *cert = list + 3, - *cert_end = cert + ((list[0] << 16) | (list[1] << 8) | list[2]); - if (cert_end > list_end) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Certificate rec missing or incomplete."); - - // Certificate - cert_context c; - if (!c.create(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, cert, (DWORD)(cert_end - cert))) - throw win_runtime_error(__FUNCTION__ " Error reading certificate."); - m_server_cert_chain.push_back(std::move(c)); - - list = cert_end; - } - - wstring cert_name(!m_server_cert_chain.empty() ? get_cert_title(m_server_cert_chain.front()) : L""); - m_module.log_event(&EAPMETHOD_TLS_CERTIFICATE, event_data((unsigned int)eap_type_tls), event_data(cert_name), event_data::blank); - break; - } - - case tls_handshake_type_certificate_request: - m_module.log_event(&EAPMETHOD_TLS_CERTIFICATE_REQUEST, event_data((unsigned int)eap_type_tls), event_data::blank); - break; - - case tls_handshake_type_server_hello_done: - m_module.log_event(&EAPMETHOD_TLS_SERVER_HELLO_DONE, event_data((unsigned int)eap_type_tls), event_data::blank); - break; - - case tls_handshake_type_finished: { - if (!m_state_server.m_alg_encrypt) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Finished message should be encrypted."); - - // According to https://tools.ietf.org/html/rfc5246#section-7.4.9 all verify_data is 12B. - if (rec_end - rec != 12) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " Finished record size incorrect (expected 12B, received %uB).", rec_end - rec)); - - // Create label + hash MD5 + hash SHA-1 seed. - crypt_hash hash; - static const unsigned char s_label[] = "server finished"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1), hash_data; - if (m_tls_version < tls_version_1_2) { - hash = m_hash_handshake_msgs_md5; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing MD5 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - hash = m_hash_handshake_msgs_sha1; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing SHA-1 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - } else { - hash = m_hash_handshake_msgs_sha256; // duplicate - if (!CryptGetHashParam(hash, HP_HASHVAL, hash_data, 0)) - throw win_runtime_error(__FUNCTION__ " Error finishing SHA-256 hash calculation."); - seed.insert(seed.end(), hash_data.begin(), hash_data.end()); - } - - if (memcmp(prf(m_cp, m_alg_prf, m_master_secret, seed, 12).data(), rec, 12)) - throw win_runtime_error(ERROR_ENCRYPTION_FAILED, __FUNCTION__ " Integrity check failed."); - - m_module.log_event(&EAPMETHOD_TLS_FINISHED, event_data((unsigned int)eap_type_tls), event_data::blank); - break; - } - - default: - m_module.log_event(&EAPMETHOD_TLS_HANDSHAKE_IGNORE, event_data((unsigned int)eap_type_tls), event_data((unsigned char)type), event_data::blank); - } - - if (type < tls_handshake_type_max) { - // Set the flag this handshake message was received. - m_handshake.set(type); - } - - if (type != tls_handshake_type_hello_request) { - // Hash all but hello requests (https://tools.ietf.org/html/rfc5246#section-7.4.1.1). - hash_handshake(msg, rec_end - msg); - } - - msg = rec_end; - } -} - -#else - -EapPeerMethodResponseAction eap::method_tls::process_handshake() -{ - // Prepare input buffer(s). - SecBuffer buf_in[] = { - { - (unsigned long)m_sc_queue.size(), - SECBUFFER_TOKEN, - m_sc_queue.data() - }, - { 0, SECBUFFER_EMPTY, NULL }, - }; - SecBufferDesc buf_in_desc = { SECBUFFER_VERSION, _countof(buf_in), buf_in }; - - // Prepare output buffer(s). - SecBuffer buf_out[] = { - { 0, SECBUFFER_TOKEN, NULL }, - { 0, SECBUFFER_ALERT, NULL }, - }; - sec_buffer_desc buf_out_desc(buf_out, _countof(buf_out)); - - SECURITY_STATUS status; - if (m_phase == phase_handshake_init) { - status = m_sc_ctx.initialize( - m_sc_cred, - !m_sc_target_name.empty() ? m_sc_target_name.c_str() : NULL, - ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | ISC_REQ_STREAM | /*ISC_REQ_USE_SUPPLIED_CREDS |*/ ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY, - 0, - &buf_in_desc, - &buf_out_desc); - - // In a desparate attempt to make Schannel remember and resume the TLS session, we send it the SCHANNEL_SESSION_TOKEN/SSL_SESSION_ENABLE_RECONNECTS - SCHANNEL_SESSION_TOKEN token_session = { SCHANNEL_SESSION, SSL_SESSION_ENABLE_RECONNECTS }; - SecBuffer token[] = { { sizeof(token_session), SECBUFFER_TOKEN, &token_session } }; - SecBufferDesc token_desc = { SECBUFFER_VERSION, _countof(token), token }; - ApplyControlToken(m_sc_ctx, &token_desc); - } else { - status = m_sc_ctx.process( - m_sc_cred, - !m_sc_target_name.empty() ? m_sc_target_name.c_str() : NULL, - ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | ISC_REQ_STREAM | /*ISC_REQ_USE_SUPPLIED_CREDS |*/ ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY, - 0, - &buf_in_desc, - &buf_out_desc); - } - -#if EAP_TLS < EAP_TLS_SCHANNEL_FULL - if (status == SEC_E_OK) - verify_server_trust(); -#endif - - if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) { - // Send Schannel's token via EAP. - assert(buf_out[0].BufferType == SECBUFFER_TOKEN); - assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); - m_packet_res.m_data.assign(reinterpret_cast(buf_out[0].pvBuffer), reinterpret_cast(buf_out[0].pvBuffer) + buf_out[0].cbBuffer); - if (buf_in[1].BufferType == SECBUFFER_EXTRA) { - // Server appended extra data. - m_sc_queue.erase(m_sc_queue.begin(), m_sc_queue.end() - buf_in[1].cbBuffer); - } else - m_sc_queue.clear(); - - if (status == SEC_E_OK) { - SecPkgContext_Authority auth; - if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_AUTHORITY, &auth))) { - m_module.log_event(&EAPMETHOD_TLS_QUERY_FAILED, event_data((unsigned int)SECPKG_ATTR_AUTHORITY), event_data(status), event_data::blank); - auth.sAuthorityName = _T(""); - } - - SecPkgContext_ConnectionInfo info; - if (SUCCEEDED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_CONNECTION_INFO, &info))) - m_module.log_event(&EAPMETHOD_TLS_HANDSHAKE_FINISHED, - event_data((unsigned int)eap_type_tls), - event_data(auth.sAuthorityName), - event_data(info.dwProtocol), - event_data(info.aiCipher), - event_data(info.dwCipherStrength), - event_data(info.aiHash), - event_data(info.dwHashStrength), - event_data(info.aiExch), - event_data(info.dwExchStrength), - event_data::blank); - else - m_module.log_event(&EAPMETHOD_TLS_QUERY_FAILED, event_data((unsigned int)SECPKG_ATTR_CONNECTION_INFO), event_data(status), event_data::blank); - - // Derive challenge for inner authentication (if any). - derive_challenge(); - - m_phase = phase_application_data; - process_application_data(m_sc_queue.data(), m_sc_queue.size()); - } else { - m_phase = phase_handshake_cont; - m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point. - } - return EapPeerMethodResponseActionSend; - } else if (status == SEC_E_INCOMPLETE_MESSAGE) { - // Schannel neeeds more data. Send ACK packet to server to send more. - return EapPeerMethodResponseActionSend; - } else if (FAILED(status)) { - if (m_sc_ctx.m_attrib & ISC_RET_EXTENDED_ERROR) { - // Send alert. - assert(buf_out[1].BufferType == SECBUFFER_ALERT); - assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); - m_packet_res.m_data.assign(reinterpret_cast(buf_out[1].pvBuffer), reinterpret_cast(buf_out[1].pvBuffer) + buf_out[1].cbBuffer); - return EapPeerMethodResponseActionSend; - } else - throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); - } - - return EapPeerMethodResponseActionNone; -} - - -EapPeerMethodResponseAction eap::method_tls::process_application_data() -{ - if (m_sc_queue.empty()) { - // An ACK packet received. Nothing to unencrypt. - return process_application_data(NULL, 0); - } - - if (!(m_sc_ctx.m_attrib & ISC_RET_CONFIDENTIALITY)) - throw runtime_error(__FUNCTION__ " Connection is not encrypted."); - - // Prepare input/output buffer(s). - SecBuffer buf[] = { - { - (unsigned long)m_sc_queue.size(), - SECBUFFER_DATA, - m_sc_queue.data() - }, - { 0, SECBUFFER_EMPTY, NULL }, - { 0, SECBUFFER_EMPTY, NULL }, - { 0, SECBUFFER_EMPTY, NULL }, - }; - SecBufferDesc buf_desc = { - SECBUFFER_VERSION, - _countof(buf), - buf - }; - - // Decrypt the message. - SECURITY_STATUS status = DecryptMessage(m_sc_ctx, &buf_desc, 0, NULL); - if (status == SEC_E_OK) { - EapPeerMethodResponseAction action = EapPeerMethodResponseActionNone; - - std::vector extra; - for (size_t i = 0; i < _countof(buf); i++) { - switch (buf[i].BufferType) { - case SECBUFFER_DATA: - // Process data. - action = process_application_data(buf[i].pvBuffer, buf[i].cbBuffer); - if (action == EapPeerMethodResponseActionDiscard) { - // Request is to be discarded as a whole. - m_sc_queue.clear(); - return EapPeerMethodResponseActionDiscard; - } - break; - - case SECBUFFER_EXTRA: - // Queue data for the next time. - extra.insert(extra.end(), reinterpret_cast(buf[i].pvBuffer), reinterpret_cast(buf[i].pvBuffer) + buf[i].cbBuffer); - break; - } - } - m_sc_queue = std::move(extra); - - return action; - } else if (status == SEC_E_INCOMPLETE_MESSAGE) { - // Schannel neeeds more data. Send ACK packet to server to send more. - return EapPeerMethodResponseActionSend; - } else if (status == SEC_I_CONTEXT_EXPIRED) { - // Server initiated connection shutdown. - m_sc_queue.clear(); - m_phase = phase_shutdown; - return EapPeerMethodResponseActionNone; - } else if (status == SEC_I_RENEGOTIATE) { - // Re-negotiation required. - m_phase = phase_handshake_init; - m_sc_queue.clear(); - return process_handshake(); - } else if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); - - return EapPeerMethodResponseActionNone; -} - -#endif - - -EapPeerMethodResponseAction eap::method_tls::process_application_data(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg) -{ - UNREFERENCED_PARAMETER(msg); - UNREFERENCED_PARAMETER(size_msg); - - return EapPeerMethodResponseActionNone; -} - - -#if EAP_TLS < EAP_TLS_SCHANNEL_FULL - -void eap::method_tls::verify_server_trust() const -{ -#if EAP_TLS < EAP_TLS_SCHANNEL - assert(!m_server_cert_chain.empty()); - const cert_context &cert = m_server_cert_chain.front(); -#else - cert_context cert; - SECURITY_STATUS status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&cert); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error retrieving server certificate from Schannel."); -#endif - - for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) { - if (cert->cbCertEncoded == (*c)->cbCertEncoded && - memcmp(cert->pbCertEncoded, (*c)->pbCertEncoded, cert->cbCertEncoded) == 0) - { - // Server certificate found directly on the trusted root CA list. - m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED_EX, event_data::blank); - return; - } - } - - // Check server name. - if (!m_cfg.m_server_names.empty()) { - bool - has_san = false, - found = false; - - // Search subjectAltName2 and subjectAltName. - for (DWORD idx_ext = 0; !found && idx_ext < cert->pCertInfo->cExtension; idx_ext++) { - unique_ptr > san_info; - if (strcmp(cert->pCertInfo->rgExtension[idx_ext].pszObjId, szOID_SUBJECT_ALT_NAME2) == 0) { - unsigned char *output = NULL; - DWORD size_output; - if (!CryptDecodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_SUBJECT_ALT_NAME2, - cert->pCertInfo->rgExtension[idx_ext].Value.pbData, cert->pCertInfo->rgExtension[idx_ext].Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_ENABLE_PUNYCODE_FLAG, - NULL, - &output, &size_output)) - throw win_runtime_error(__FUNCTION__ " Error decoding subjectAltName2 certificate extension."); - san_info.reset((CERT_ALT_NAME_INFO*)output); - } else if (strcmp(cert->pCertInfo->rgExtension[idx_ext].pszObjId, szOID_SUBJECT_ALT_NAME) == 0) { - unsigned char *output = NULL; - DWORD size_output; - if (!CryptDecodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_SUBJECT_ALT_NAME, - cert->pCertInfo->rgExtension[idx_ext].Value.pbData, cert->pCertInfo->rgExtension[idx_ext].Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_ENABLE_PUNYCODE_FLAG, - NULL, - &output, &size_output)) - throw win_runtime_error(__FUNCTION__ " Error decoding subjectAltName certificate extension."); - san_info.reset((CERT_ALT_NAME_INFO*)output); - } else { - // Skip this extension. - continue; - } - has_san = true; - - for (auto s = m_cfg.m_server_names.cbegin(), s_end = m_cfg.m_server_names.cend(); !found && s != s_end; ++s) { - for (DWORD idx_entry = 0; !found && idx_entry < san_info->cAltEntry; idx_entry++) { - if (san_info->rgAltEntry[idx_entry].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME && - _wcsicmp(s->c_str(), san_info->rgAltEntry[idx_entry].pwszDNSName) == 0) - { - m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED1, event_data(san_info->rgAltEntry[idx_entry].pwszDNSName), event_data::blank); - found = true; - } - } - } - } - - if (!has_san) { - // Certificate has no subjectAltName. Compare against Common Name. - wstring subj; - if (!CertGetNameStringW(cert, CERT_NAME_DNS_TYPE, CERT_NAME_STR_ENABLE_PUNYCODE_FLAG, NULL, subj)) - throw win_runtime_error(__FUNCTION__ " Error retrieving server's certificate subject name."); - - for (auto s = m_cfg.m_server_names.cbegin(), s_end = m_cfg.m_server_names.cend(); !found && s != s_end; ++s) { - if (_wcsicmp(s->c_str(), subj.c_str()) == 0) { - m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED1, event_data(subj), event_data::blank); - found = true; - } - } - } - - if (!found) - throw sec_runtime_error(SEC_E_WRONG_PRINCIPAL, __FUNCTION__ " Name provided in server certificate is not on the list of trusted server names."); - } - - if (cert->pCertInfo->Issuer.cbData == cert->pCertInfo->Subject.cbData && - memcmp(cert->pCertInfo->Issuer.pbData, cert->pCertInfo->Subject.pbData, cert->pCertInfo->Issuer.cbData) == 0) - throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Server is using a self-signed certificate. Cannot trust it."); - - // Create temporary certificate store of our trusted root CAs. - cert_store store; - if (!store.create(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, NULL)) - throw win_runtime_error(__FUNCTION__ " Error creating temporary certificate store."); - for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) - CertAddCertificateContextToStore(store, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); - - // Add all intermediate certificates from the server's certificate chain. -#if EAP_TLS < EAP_TLS_SCHANNEL - for (auto c = m_server_cert_chain.cbegin(), c_end = m_server_cert_chain.cend(); ++c != c_end;) { - const cert_context &_c = *c; - if (_c->pCertInfo->Issuer.cbData == _c->pCertInfo->Subject.cbData && - memcmp(_c->pCertInfo->Issuer.pbData, _c->pCertInfo->Subject.pbData, _c->pCertInfo->Issuer.cbData) == 0) - { - // Skip the root CA certificates (self-signed). We define in whom we trust! - continue; - } - - CertAddCertificateContextToStore(store, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); - } -#else - for (cert_context c(cert); c;) { - DWORD flags = 0; - c.attach(CertGetIssuerCertificateFromStore(cert->hCertStore, c, NULL, &flags)); - if (!c) break; - - if (c->pCertInfo->Issuer.cbData == c->pCertInfo->Subject.cbData && - memcmp(c->pCertInfo->Issuer.pbData, c->pCertInfo->Subject.pbData, c->pCertInfo->Issuer.cbData) == 0) - { - // Skip the root CA certificates (self-signed). We define in whom we trust! - continue; - } - - CertAddCertificateContextToStore(store, c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); - } -#endif - - // Prepare the certificate chain validation, and check. - CERT_CHAIN_PARA chain_params = { - sizeof(chain_params), // cbSize - { - USAGE_MATCH_TYPE_AND, // RequestedUsage.dwType - {}, // RequestedUsage.Usage - }, -#ifdef CERT_CHAIN_PARA_HAS_EXTRA_FIELDS - {}, // RequestedIssuancePolicy - 1, // dwUrlRetrievalTimeout (1ms to speed up checking) -#else -#define _S2(x) #x -#define _S(x) _S2(x) -#pragma message(__FILE__ "(" _S(__LINE__) "): warning X0000: Please define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS constant when compiling this project.") -#endif - }; - cert_chain_context context; - if (!context.create(NULL, cert, NULL, store, &chain_params, 0)) - throw win_runtime_error(__FUNCTION__ " Error creating certificate chain context."); - - // Check chain validation error flags. Ignore CERT_TRUST_IS_UNTRUSTED_ROOT flag since we check root CA explicitly. - if (context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR && - (context->TrustStatus.dwErrorStatus & ~CERT_TRUST_IS_UNTRUSTED_ROOT) != CERT_TRUST_NO_ERROR) - { - if (context->TrustStatus.dwErrorStatus & (CERT_TRUST_IS_NOT_TIME_VALID | CERT_TRUST_IS_NOT_TIME_NESTED)) - throw sec_runtime_error(SEC_E_CERT_EXPIRED, __FUNCTION__ " Server certificate has expired (or is not valid yet)."); - else if (context->TrustStatus.dwErrorStatus & (CERT_TRUST_IS_UNTRUSTED_ROOT | CERT_TRUST_IS_PARTIAL_CHAIN)) - throw sec_runtime_error(SEC_E_UNTRUSTED_ROOT, __FUNCTION__ " Server's certificate not issued by one of configured trusted root CAs."); - else - throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Error validating server certificate."); - } - - // Verify Root CA against our trusted root CA list - if (context->cChain != 1) - throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Multiple chain verification not supported."); - if (context->rgpChain[0]->cElement == 0) - throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Can not verify empty certificate chain."); - - PCCERT_CONTEXT cert_root = context->rgpChain[0]->rgpElement[context->rgpChain[0]->cElement-1]->pCertContext; - for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend();; ++c) { - if (c != c_end) { - if (cert_root->cbCertEncoded == (*c)->cbCertEncoded && - memcmp(cert_root->pbCertEncoded, (*c)->pbCertEncoded, cert_root->cbCertEncoded) == 0) - { - // Trusted root CA found. - break; - } - } else { - // Not found. - throw sec_runtime_error(SEC_E_UNTRUSTED_ROOT, __FUNCTION__ " Server's certificate not issued by one of configured trusted root CAs."); - } - } - - m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED, event_data::blank); -} - -#endif - -#if EAP_TLS < EAP_TLS_SCHANNEL - -void eap::method_tls::encrypt_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &data) -{ - // Hash sequence number, TLS header, and message. - size_t size_data = data.size(); - hmac_hash hash(m_cp, m_state_client.m_alg_mac, m_state_client.m_padding_hmac); - unsigned __int64 seq_num2 = htonll(m_seq_num_client); - unsigned short size_data2 = htons((unsigned short)size_data); - if (!CryptHashData(hash, (const BYTE*)&seq_num2 , sizeof(seq_num2 ), 0) || - !CryptHashData(hash, (const BYTE*)&type , sizeof(type ), 0) || - !CryptHashData(hash, (const BYTE*)&m_tls_version, sizeof(m_tls_version), 0) || - !CryptHashData(hash, (const BYTE*)&size_data2 , sizeof(size_data2 ), 0) || - !CryptHashData(hash, data.data() , (DWORD)size_data , 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing data."); - sanitizing_blob hmac; - hash.calculate(hmac); - - size_t size_data_enc = - size_data + // TLS message - hmac.size(); // HMAC hash - - if (m_state_client.m_size_enc_block) { - // Block cypher - - if (m_tls_version >= tls_version_1_1) { - // TLS 1.1+: Set random IV. - data.insert(data.begin(), m_state_client.m_size_enc_iv, 0); - if (!CryptGenRandom(m_cp, (DWORD)m_state_client.m_size_enc_iv, data.data())) - throw win_runtime_error(__FUNCTION__ " Error generating IV."); - size_data_enc += m_state_client.m_size_enc_iv; - } - - // Calculate padding. - size_data_enc += 1; // Padding length - unsigned char size_padding = (unsigned char)((m_state_client.m_size_enc_block - size_data_enc) % m_state_client.m_size_enc_block); - size_data_enc += size_padding; - - // Append HMAC hash and padding. - data.reserve(size_data_enc); - data.insert(data.end(), hmac.begin(), hmac.end()); - data.insert(data.end(), size_padding + 1, size_padding); - } else { - // Stream cipher - - // Append HMAC hash. - data.reserve(size_data_enc); - data.insert(data.end(), hmac.begin(), hmac.end()); - } - - // Encrypt. - assert(size_data_enc < 0xffffffff); - DWORD size_data_enc2 = (DWORD)size_data_enc; - if (!CryptEncrypt(m_state_client.m_key, NULL, FALSE, 0, data.data(), &size_data_enc2, (DWORD)size_data_enc)) - throw win_runtime_error(__FUNCTION__ " Error encrypting message."); - - // Increment sequence number. - m_seq_num_client++; -} - - -void eap::method_tls::decrypt_message(_In_ tls_message_type_t type, _Inout_ sanitizing_blob &data) -{ - // Decrypt. - if (!CryptDecrypt(m_state_server.m_key, NULL, FALSE, 0, data)) - throw win_runtime_error(__FUNCTION__ " Error decrypting message."); - - if (!data.empty()) { - size_t size_data = data.size(); - bool padding_ok = true; - - if (m_state_server.m_size_enc_block) { - // Check padding. Do not throw until HMAC is calculated. - // [Canvel, B., "Password Interception in a SSL/TLS Channel"](http://lasecwww.epfl.ch/memo_ssl.shtml) - unsigned char padding = data.back(); - size_data = (size_t)padding + 1 <= size_data ? size_data - (padding + 1) : 0; - for (size_t i = size_data, i_end = data.size() - 1; i < i_end; i++) - if (data[i] != padding) - padding_ok = false; - - // Remove padding. - data.resize(size_data); - - if (m_tls_version >= tls_version_1_1) { - // TLS 1.1+: Remove random IV. - data.erase(data.begin(), data.begin() + m_state_server.m_size_enc_iv); - size_data -= m_state_server.m_size_enc_iv; - } - } - - size_data -= m_state_server.m_size_mac_hash; - - // Hash sequence number, TLS header (without length), original message length, and message. - hmac_hash hash(m_cp, m_state_server.m_alg_mac, m_state_server.m_padding_hmac); - unsigned __int64 seq_num2 = htonll(m_seq_num_server); - unsigned short size_data2 = htons((unsigned short)size_data); - if (!CryptHashData(hash, (const BYTE*)&seq_num2 , sizeof(seq_num2 ), 0) || - !CryptHashData(hash, (const BYTE*)&type , sizeof(type ), 0) || - !CryptHashData(hash, (const BYTE*)&m_tls_version, sizeof(m_tls_version), 0) || - !CryptHashData(hash, (const BYTE*)&size_data2 , sizeof(size_data2 ), 0) || - !CryptHashData(hash, data.data() , (DWORD)size_data , 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing data."); - sanitizing_blob hmac; - hash.calculate(hmac); - - // // Check padding results. - if (!padding_ok) - throw invalid_argument(__FUNCTION__ " Incorrect message padding."); - - // Verify hash. - if (memcmp(&*(data.begin() + size_data), hmac.data(), m_state_server.m_size_mac_hash) != 0) - throw win_runtime_error(ERROR_DECRYPTION_FAILED, __FUNCTION__ " Integrity check failed."); - - // Strip hash and padding. - data.resize(size_data); - - // Increment sequence number. - m_seq_num_server++; - } -} - - -size_t eap::method_tls::get_max_message(_In_ size_t size_message) const -{ - if (m_state_client.m_size_enc_block) { - // Padding - size_message -= size_message % m_state_client.m_size_enc_block; - size_message--; - - // HMAC - size_message -= m_state_client.m_size_mac_hash; - - if (m_tls_version >= tls_version_1_1) { - // IV (TLS 1.1+) - size_message -= m_state_client.m_size_enc_iv; - } - } else { - // HMAC - size_message -= m_state_client.m_size_mac_hash; - } - - return size_message; -} - - -eap::sanitizing_blob eap::method_tls::prf( - _In_ HCRYPTPROV cp, - _In_ ALG_ID alg, - _In_ const tls_master_secret &secret, - _In_bytecount_(size_seed) const void *seed, - _In_ size_t size_seed, - _In_ size_t size) -{ - sanitizing_blob data; - data.reserve(size); - - if (alg == CALG_TLS1PRF) { - // Split secret in two halves. - size_t - size_S1 = (sizeof(tls_master_secret) + 1) / 2, - size_S2 = size_S1; - const void - *S1 = &secret, - *S2 = reinterpret_cast(&secret) + (sizeof(tls_master_secret) - size_S2); - - // Precalculate HMAC padding for speed. - hmac_padding - padding1(cp, CALG_MD5 , S1, size_S1), - padding2(cp, CALG_SHA1, S2, size_S2); - - // Prepare A for p_hash. - sanitizing_blob - A1(reinterpret_cast(seed), reinterpret_cast(seed) + size_seed), - A2(reinterpret_cast(seed), reinterpret_cast(seed) + size_seed); - - sanitizing_blob - hmac1, - hmac2; - data.resize(size); - for (size_t i = 0, off1 = 0, off2 = 0; i < size; ) { - if (off1 >= hmac1.size()) { - // Rehash A. - hmac_hash hash1(cp, CALG_MD5 , padding1); - if (!CryptHashData(hash1, A1.data(), (DWORD)A1.size(), 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing A1."); - hash1.calculate(A1); - - // Hash A and seed. - hmac_hash hash2(cp, CALG_MD5 , padding1); - if (!CryptHashData(hash2, A1.data(), (DWORD)A1.size(), 0) || - !CryptHashData(hash2, (const BYTE*)seed , (DWORD)size_seed, 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing seed,label or data."); - hash2.calculate(hmac1); - off1 = 0; - } - - if (off2 >= hmac2.size()) { - // Rehash A. - hmac_hash hash1(cp, CALG_SHA1 , padding2); - if (!CryptHashData(hash1, A2.data(), (DWORD)A2.size(), 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing A2."); - hash1.calculate(A2); - - // Hash A and seed. - hmac_hash hash2(cp, CALG_SHA1 , padding2); - if (!CryptHashData(hash2, A2.data(), (DWORD)A2.size(), 0) || - !CryptHashData(hash2, (const BYTE*)seed , (DWORD)size_seed, 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing seed,label or data."); - hash2.calculate(hmac2); - off2 = 0; - } - - // XOR combine amount of data we have (and need). - size_t i_end = std::min(i + std::min(hmac1.size() - off1, hmac2.size() - off2), size); - while (i < i_end) - data[i++] = hmac1[off1++] ^ hmac2[off2++]; - } - } else { - // Precalculate HMAC padding for speed. - hmac_padding padding(cp, alg, &secret, sizeof(tls_master_secret)); - - // Prepare A for p_hash. - sanitizing_blob A(reinterpret_cast(seed), reinterpret_cast(seed) + size_seed); - - sanitizing_blob hmac; - for (size_t i = 0; i < size; ) { - // Rehash A. - hmac_hash hash1(cp, alg, padding); - if (!CryptHashData(hash1, A.data(), (DWORD)A.size(), 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing A."); - hash1.calculate(A); - - // Hash A and seed. - hmac_hash hash2(cp, alg, padding); - if (!CryptHashData(hash2, A.data(), (DWORD)A.size() , 0) || - !CryptHashData(hash2, (const BYTE*)seed , (DWORD)size_seed, 0)) - throw win_runtime_error(__FUNCTION__ " Error hashing seed,label or data."); - hash2.calculate(hmac); - - size_t n = std::min(hmac.size(), size - i); - data.insert(data.end(), hmac.begin(), hmac.begin() + n); - i += n; - } - } - - return data; -} - - -HCRYPTKEY eap::method_tls::create_key( - _In_ HCRYPTPROV cp, - _In_ ALG_ID alg, - _In_ HCRYPTKEY key, - _In_bytecount_(size_secret) const void *secret, - _In_ size_t size_secret) -{ -#if 1 - UNREFERENCED_PARAMETER(key); - assert(size_secret <= 0xffffffff); - - // Prepare exported key BLOB. - struct key_blob_prefix { - PUBLICKEYSTRUC header; - DWORD size; - } const prefix = { - { - PLAINTEXTKEYBLOB, - CUR_BLOB_VERSION, - 0, - alg, - }, - (DWORD)size_secret, - }; - sanitizing_blob key_blob; - key_blob.reserve(sizeof(key_blob_prefix) + size_secret); - key_blob.assign( reinterpret_cast(&prefix), reinterpret_cast(&prefix + 1)); - key_blob.insert(key_blob.end(), reinterpret_cast( secret), reinterpret_cast(secret) + size_secret); - - // Import the key. - winstd::crypt_key key_out; - if (!key_out.import(cp, key_blob.data(), (DWORD)key_blob.size(), NULL, 0)) - throw winstd::win_runtime_error(__FUNCTION__ " Error importing key."); - return key_out.detach(); -#else - // Get private key's algorithm. - ALG_ID alg_key; - if (!CryptGetKeyParam(key, KP_ALGID, alg_key, 0)) - throw win_runtime_error(__FUNCTION__ " Error getting key's algorithm.'"); - - // Get private key's length in bytes. - DWORD size_key = CryptGetKeyParam(key, KP_KEYLEN, size_key, 0) ? size_key/8 : 0; - - // SIMPLEBLOB Format is documented in SDK - // Copy header to buffer -#pragma pack(push) -#pragma pack(1) - struct key_blob_prefix { - PUBLICKEYSTRUC header; - ALG_ID alg; - } const prefix = { - { - SIMPLEBLOB, - CUR_BLOB_VERSION, - 0, - alg, - }, - alg_key, - }; -#pragma pack(pop) - sanitizing_blob key_blob; - key_blob.reserve(sizeof(key_blob_prefix) + size_key); - key_blob.assign(reinterpret_cast(&prefix), reinterpret_cast(&prefix + 1)); - - // Key in EME-PKCS1-v1_5 (RFC 3447). - key_blob.push_back(0); // Initial zero - key_blob.push_back(2); // PKCS #1 block type = 2 - - // PS - size_t size_ps = size_key - size_secret - 3; - assert(size_ps >= 8); -#if 1 - key_blob.insert(key_blob.end(), size_ps, 1); -#else - // Is random PS required at all? We are importing a clear-text session key with the exponent-of-one key. How low on security can we get? - key_blob.insert(key_blob.end(), size_ps, 0); - unsigned char *ps = &*(key_blob.end() - size_ps); - CryptGenRandom(cp, (DWORD)size_ps, ps); - for (size_t i = 0; i < size_ps; i++) - if (ps[i] == 0) ps[i] = 1; -#endif - - key_blob.push_back(0); // PS and M zero delimiter - - // M - key_blob.insert(key_blob.end(), reinterpret_cast(secret), reinterpret_cast(secret) + size_secret); - -#ifdef _HOST_LOW_ENDIAN - std::reverse(key_blob.end() - size_key, key_blob.end()); -#endif - - // Import the key. - winstd::crypt_key key_out; - if (!key_out.import(cp, key_blob.data(), (DWORD)key_blob.size(), key, 0)) - throw winstd::win_runtime_error(__FUNCTION__ " Error importing key."); - return key_out.detach(); -#endif -} - -#endif diff --git a/lib/TLS/src/StdAfx.h b/lib/TLS/src/StdAfx.h index 70f2064..22c9463 100644 --- a/lib/TLS/src/StdAfx.h +++ b/lib/TLS/src/StdAfx.h @@ -22,7 +22,6 @@ #include "../include/Config.h" #include "../include/Credentials.h" -#include "../include/Method.h" #include "../include/TLS.h" #include "../../EAPBase/include/EAPXML.h" diff --git a/lib/TLS/src/TLS.cpp b/lib/TLS/src/TLS.cpp index c511951..84df3f4 100644 --- a/lib/TLS/src/TLS.cpp +++ b/lib/TLS/src/TLS.cpp @@ -428,7 +428,7 @@ void eap::tls_conn_state::set_cipher(_In_ const unsigned char cipher[2]) m_size_mac_key = 384/8; // SHA-384 m_size_mac_hash = 384/8; // SHA-384 } else - throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Unknown cipher (received 0x%02x%02x).", cipher[0], cipher[1])); + throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Unknown cipher (received: 0x%02x%02x).", cipher[0], cipher[1])); } diff --git a/lib/TLS_UI/include/TLS_UI.h b/lib/TLS_UI/include/TLS_UI.h index 38b9160..acd60a1 100644 --- a/lib/TLS_UI/include/TLS_UI.h +++ b/lib/TLS_UI/include/TLS_UI.h @@ -172,9 +172,6 @@ public: protected: /// \cond internal virtual void OnInitDialog(wxInitDialogEvent& event); -#if EAP_TLS < EAP_TLS_SCHANNEL - virtual bool TransferDataFromWindow(); -#endif /// \endcond protected: diff --git a/lib/TLS_UI/src/TLS_UI.cpp b/lib/TLS_UI/src/TLS_UI.cpp index 6789292..f6bad4a 100644 --- a/lib/TLS_UI/src/TLS_UI.cpp +++ b/lib/TLS_UI/src/TLS_UI.cpp @@ -339,23 +339,3 @@ void wxTLSConfigPanel::OnInitDialog(wxInitDialogEvent& event) if (m_credentials) m_credentials->GetEventHandler()->ProcessEvent(event); } - - -#if EAP_TLS < EAP_TLS_SCHANNEL - -bool wxTLSConfigPanel::TransferDataFromWindow() -{ - wxCHECK(wxPanel::TransferDataFromWindow(), false); - - if (!m_prov.m_read_only) { - // This is not a provider-locked configuration. The data will get saved. - - // Reset session ID and master secret to force clean connect next time. - m_cfg.m_session_id.clear(); - m_cfg.m_master_secret.clear(); - } - - return true; -} - -#endif diff --git a/lib/TTLS/include/Method.h b/lib/TTLS/include/Method.h index 870f559..d3c771f 100644 --- a/lib/TTLS/include/Method.h +++ b/lib/TTLS/include/Method.h @@ -21,7 +21,17 @@ namespace eap { /// - /// EAP-TTLS method + /// EAP-(T)TLS class defragging method tunnel + /// + class method_defrag; + + /// + /// Diameter EAP-Message tunnel method + /// + class method_eapmsg; + + /// + /// TTLS method /// class method_ttls; } @@ -32,37 +42,203 @@ namespace eap #include "Credentials.h" #include "TTLS.h" -#include "../../TLS/include/Method.h" #include "../../EAPBase/include/Method.h" +#include + namespace eap { - class method_ttls : public method_tls + class method_defrag : public method_tunnel + { + WINSTD_NONCOPYABLE(method_defrag) + + public: +#pragma warning(push) +#pragma warning(disable: 4480) + + /// + /// EAP-(T)TLS request packet flags + /// + /// \sa [The EAP-TLS Authentication Protocol (Chapter: 3.1 EAP-TLS Request Packet)](https://tools.ietf.org/html/rfc5216#section-3.1) + /// \sa [The EAP-TTLS Authentication Protocol Version 0 (Chapter: 9.1. Packet Format)](https://tools.ietf.org/html/rfc5281#section-9.1) + /// + enum flags_req_t : unsigned char { + flags_req_length_incl = 0x80, ///< Length included + flags_req_more_frag = 0x40, ///< More fragments + flags_req_start = 0x20, ///< Start + flags_req_ver_mask = 0x07, ///< Version mask + }; + + /// + /// EAP-(T)TLS response packet flags + /// + /// \sa [The EAP-TLS Authentication Protocol (Chapter: 3.2 EAP-TLS Response Packet)](https://tools.ietf.org/html/rfc5216#section-3.2) + /// \sa [The EAP-TTLS Authentication Protocol Version 0 (Chapter: 9.1. Packet Format)](https://tools.ietf.org/html/rfc5281#section-9.1) + /// + enum flags_res_t : unsigned char { + flags_res_length_incl = 0x80, ///< Length included + flags_res_more_frag = 0x40, ///< More fragments + flags_res_ver_mask = 0x07, ///< Version mask + }; + +#pragma warning(pop) + + public: + /// + /// Constructs a method + /// + /// \param[in] mod Module to use for global services + /// \param[in] inner Inner method + /// + method_defrag(_In_ module &mod, _In_ method *inner); + + /// + /// Moves a method + /// + /// \param[in] other Method to move from + /// + method_defrag(_Inout_ method_defrag &&other); + + /// + /// Moves a method + /// + /// \param[in] other Method to move from + /// + /// \returns Reference to this object + /// + method_defrag& operator=(_Inout_ method_defrag &&other); + + /// \name Packet processing + /// @{ + + /// + /// Processes a packet received by EapHost from a supplicant. + /// + /// \sa [EapPeerProcessRequestPacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363621.aspx) + /// + virtual EapPeerMethodResponseAction process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize); + + /// + /// Obtains a response packet from the EAP method. + /// + /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) + /// + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + protected: + DWORD m_size_frag_max; ///< Maximum size of a fragment + sanitizing_blob m_data_req; ///< Data in request + sanitizing_blob m_data_res; ///< Data in response + bool m_send_res; ///< Are we sending a response? + }; + + + class method_eapmsg : public method_tunnel + { + public: + /// + /// Constructs a method + /// + /// \param[in] mod Module to use for global services + /// \param[in] identity User identity + /// \param[in] inner Inner method + /// + method_eapmsg(_In_ module &mod, _In_ const wchar_t *identity, _In_ method *inner); + + /// + /// Moves a method + /// + /// \param[in] other Method to move from + /// + method_eapmsg(_Inout_ method_eapmsg &&other); + + /// + /// Moves a method + /// + /// \param[in] other Method to move from + /// + /// \returns Reference to this object + /// + method_eapmsg& operator=(_Inout_ method_eapmsg &&other); + + /// \name Packet processing + /// @{ + + /// + /// Starts an EAP authentication session on the peer EapHost using the EAP method. + /// + /// \sa [EapPeerBeginSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363600.aspx) + /// + virtual void begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD); + + /// + /// Processes a packet received by EapHost from a supplicant. + /// + /// \sa [EapPeerProcessRequestPacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363621.aspx) + /// + virtual EapPeerMethodResponseAction process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize); + + /// + /// Obtains a response packet from the EAP method. + /// + /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) + /// + virtual void get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); + + /// @} + + protected: + std::wstring m_identity; ///< User identity + + enum { + phase_unknown = -1, ///< Unknown phase + phase_identity = 0, ///< Send identity + phase_finished, ///< Connection shut down + } m_phase; ///< What phase is our communication at? + + sanitizing_blob m_packet_res; ///< Response packet + }; + + + class method_ttls : public method_tunnel { WINSTD_NONCOPYABLE(method_ttls) public: /// - /// Constructs an EAP-TTLS method + /// Constructs an TTLS method /// - /// \param[in] mod EAP module to use for global services - /// \param[in] cfg Method configuration - /// \param[in] cred User credentials + /// \param[in] mod EAP module to use for global services + /// \param[in] cfg Method configuration + /// \param[in] cred User credentials + /// \param[in] inner Inner method /// - method_ttls(_In_ module &module, _In_ config_method_ttls &cfg, _In_ credentials_ttls &cred); + method_ttls(_In_ module &mod, _In_ config_method_ttls &cfg, _In_ credentials_ttls &cred, _In_ method *inner); /// - /// Moves an EAP-TTLS method + /// Moves an TTLS method /// - /// \param[in] other EAP-TTLS method to move from + /// \param[in] other TTLS method to move from /// method_ttls(_Inout_ method_ttls &&other); /// - /// Moves an EAP-TTLS method + /// Moves an TTLS method /// - /// \param[in] other EAP-TTLS method to move from + /// \param[in] other TTLS method to move from /// /// \returns Reference to this object /// @@ -82,13 +258,6 @@ namespace eap _In_ HANDLE hTokenImpersonateUser, _In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD); - /// - /// Ends an EAP authentication session for the EAP method. - /// - /// \sa [EapPeerEndSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363604.aspx) - /// - virtual void end_session(); - /// /// Processes a packet received by EapHost from a supplicant. /// @@ -104,8 +273,8 @@ namespace eap /// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx) /// virtual void get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize); + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max = MAXDWORD); /// /// Obtains the result of an authentication session from the EAP method. @@ -118,79 +287,34 @@ namespace eap /// @} - /// \name User Interaction - /// @{ - + protected: +#if EAP_TLS < EAP_TLS_SCHANNEL_FULL /// - /// Obtains the user interface context from the EAP method. + /// Verifies server's certificate if trusted by configuration /// - /// \note This function is always followed by the `EapPeerInvokeInteractiveUI()` function, which is followed by the `EapPeerSetUIContext()` function. - /// - /// \sa [EapPeerGetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363612.aspx) - /// - virtual void get_ui_context( - _Inout_ BYTE **ppUIContextData, - _Inout_ DWORD *pdwUIContextDataSize); - - /// - /// Provides a user interface context to the EAP method. - /// - /// \note This function is called after the UI has been raised through the `EapPeerGetUIContext()` function. - /// - /// \sa [EapPeerSetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363626.aspx) - /// - virtual EapPeerMethodResponseAction set_ui_context( - _In_count_(dwUIContextDataSize) const BYTE *pUIContextData, - _In_ DWORD dwUIContextDataSize); - - /// @} - - /// \name EAP Response Attributes - /// @{ - - /// - /// Obtains an array of EAP response attributes from the EAP method. - /// - /// \sa [EapPeerGetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363609.aspx) - /// - virtual void get_response_attributes(_Inout_ EapAttributes *pAttribs); - - /// - /// Provides an updated array of EAP response attributes to the EAP method. - /// - /// \sa [EapPeerSetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363625.aspx) - /// - virtual EapPeerMethodResponseAction set_response_attributes(_In_ const EapAttributes *pAttribs); - - /// @} + void verify_server_trust() const; +#endif protected: - /// - /// Generates master session key - /// - /// \sa [The EAP-TLS Authentication Protocol (Chapter 2.3. Key Hierarchy)](https://tools.ietf.org/html/rfc5216#section-2.3) - /// - virtual void derive_msk(); + config_method_ttls &m_cfg; ///< Method configuration + credentials_ttls &m_cred; ///< Method user credentials + HANDLE m_user_ctx; ///< Handle to user context + winstd::tstring m_sc_target_name; ///< Schannel target name + winstd::sec_credentials m_sc_cred; ///< Schannel client credentials + std::vector m_sc_queue; ///< TLS data queue + winstd::sec_context m_sc_ctx; ///< Schannel context - /// - /// Generates keying material for inner authentication - /// - virtual void derive_challenge(); + enum { + phase_unknown = -1, ///< Unknown phase + phase_handshake_init = 0, ///< Handshake initialize + phase_handshake_cont, ///< Handshake continue + phase_finished, ///< Exchange application data + } m_phase; ///< What phase is our communication at? - /// - /// Processes an application message - /// - /// \param[in] msg Application message data - /// \param[in] size_msg Application message data size - /// - virtual EapPeerMethodResponseAction process_application_data(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg); + sanitizing_blob m_packet_res; ///< Response packet + bool m_packet_res_inner; ///< Get and ancrypt data from inner method too? - protected: - #pragma warning(suppress: 4480) - enum version_t :unsigned char { - version_0 = 0, ///< EAP-TTLS v0 - } m_version; ///< EAP-TTLS version - - std::unique_ptr m_inner; ///< Inner authentication method + std::vector m_eap_attr; ///< EAP attributes returned by get_result() method + EAP_ATTRIBUTES m_eap_attr_desc; ///< EAP attributes descriptor (required to avoid memory leakage in get_result()) }; } diff --git a/lib/TTLS/include/Module.h b/lib/TTLS/include/Module.h index 64d950a..af20d63 100644 --- a/lib/TTLS/include/Module.h +++ b/lib/TTLS/include/Module.h @@ -41,7 +41,7 @@ namespace eap public: /// - /// Constructs a EAP TTLS peer module + /// Constructs a EAP-TTLS peer module /// peer_ttls(); @@ -228,20 +228,26 @@ namespace eap protected: class session { public: + /// + /// Constructs a EAP-TTLS session + /// session(_In_ module &mod); + + /// + /// Destructs EAP-TTLS session + /// virtual ~session(); public: - module &m_module; ///< Module - config_connection m_cfg; ///< Connection configuration - credentials_connection m_cred; ///< Connection credentials - std::unique_ptr m_method; ///< EAP-TTLS method + module &m_module; ///< Module + config_connection m_cfg; ///< Connection configuration + credentials_connection m_cred; ///< Connection credentials + std::unique_ptr m_method; ///< EAP-TTLS method // The following members are required to avoid memory leakage in get_result() - EAP_ATTRIBUTES m_eap_attr_desc; ///< EAP attributes descriptor - BYTE *m_blob_cfg; ///< Configuration BLOB + BYTE *m_blob_cfg; ///< Configuration BLOB #ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE - BYTE *m_blob_cred; ///< Credentials BLOB + BYTE *m_blob_cred; ///< Credentials BLOB #endif }; }; diff --git a/lib/TTLS/src/Method.cpp b/lib/TTLS/src/Method.cpp index 6029e99..51a84b5 100644 --- a/lib/TTLS/src/Method.cpp +++ b/lib/TTLS/src/Method.cpp @@ -20,25 +20,322 @@ #include "StdAfx.h" +#pragma comment(lib, "Secur32.lib") + using namespace std; using namespace winstd; +////////////////////////////////////////////////////////////////////// +// eap::method_defrag +////////////////////////////////////////////////////////////////////// + +eap::method_defrag::method_defrag(_In_ module &mod, _In_ method *inner) : + m_send_res(false), + method_tunnel(mod, inner) +{ +} + + +eap::method_defrag::method_defrag(_Inout_ method_defrag &&other) : + m_data_req (std::move(other.m_data_req)), + m_data_res (std::move(other.m_data_res)), + m_send_res (std::move(other.m_send_res)), + method_tunnel(std::move(other )) +{ +} + + +eap::method_defrag& eap::method_defrag::operator=(_Inout_ method_defrag &&other) +{ + if (this != std::addressof(other)) { + (method_tunnel&)*this = std::move(other ); + m_data_req = std::move(other.m_data_req); + m_data_res = std::move(other.m_data_res); + m_send_res = std::move(other.m_send_res); + } + + return *this; +} + + +EapPeerMethodResponseAction eap::method_defrag::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize) +{ + assert(dwReceivedPacketSize >= 1); // Request packet should contain flags at least. + auto data_packet = reinterpret_cast(pReceivedPacket); + + // Get packet content pointer and size for more readable code later on. + const unsigned char *data_content; + size_t size_content; + if (data_packet[0] & flags_req_length_incl) { + // Length field is included. + data_content = data_packet + 5; + size_content = dwReceivedPacketSize - 5; + } else { + // Length field not included. + data_content = data_packet + 1; + size_content = dwReceivedPacketSize - 1; + } + + // Do the defragmentation. + if (data_packet[0] & flags_req_more_frag) { + if (m_data_req.empty()) { + // Start a new packet. + if (data_packet[0] & flags_req_length_incl) { + // Preallocate data according to the Length field. + m_data_req.reserve(ntohl(*reinterpret_cast(data_packet + 1))); + } + } + m_data_req.insert(m_data_req.end(), data_content, data_content + size_content); + + // Respond with ACK packet (empty packet). + m_data_res.clear(); + m_send_res = true; + return EapPeerMethodResponseActionSend; + } else if (!m_data_req.empty()) { + // Last fragment received. Append data. + m_data_req.insert(m_data_req.end(), data_content, data_content + size_content); + } else { + // This is a complete non-fragmented packet. + m_data_req.assign(data_content, data_content + size_content); + } + + if (m_send_res) { + // We are sending a fragmented message. + if (m_data_req.empty() && (data_packet[0] & (flags_req_length_incl | flags_req_more_frag | flags_req_start)) == 0) { + // Received packet is the ACK of our fragmented message packet. Send the next fragment. + return EapPeerMethodResponseActionSend; + } else + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " ACK expected."); + } + + // Process the data with underlying method. + auto action = method_tunnel::process_request_packet(m_data_req.data(), (DWORD)m_data_req.size()); + + // Packet was processed. Clear its data since we use the absence of data to detect first of fragmented message packages. + m_data_req.clear(); + return action; +} + + +void eap::method_defrag::get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) +{ + assert(size_max > 5); // We can not do the fragmentation if we have less space than flags+length+at least one byte of data. + + if (!m_send_res) { + // Get data from underlying method. + method_tunnel::get_response_packet(m_data_res, MAXDWORD); + } + + size_t size_data = m_data_res.size(); + assert(size_data <= MAXDWORD); // Packets spanning over 4GB are not supported. + + packet.clear(); + if (size_data + 1 > size_max) { + // Write one fragment. + packet.push_back(flags_res_length_incl | flags_res_more_frag); + unsigned int length = htonl((unsigned int)size_data); + packet.insert(packet.end(), reinterpret_cast(&length), reinterpret_cast(&length + 1)); + auto data_begin = m_data_res.begin() + 0, data_end = data_begin + (size_max - 5); + packet.insert(packet.end(), data_begin, data_end); + m_data_res.erase(data_begin, data_end); + m_send_res = true; + } else { + // Write single/last fragment. + packet.push_back(0); + packet.insert(packet.end(), m_data_res.begin(), m_data_res.end()); + m_data_res.clear(); + m_send_res = false; + } +} + + +////////////////////////////////////////////////////////////////////// +// eap::method_eapmsg +////////////////////////////////////////////////////////////////////// + +eap::method_eapmsg::method_eapmsg(_In_ module &mod, _In_ const wchar_t *identity, _In_ method *inner) : + m_identity(identity), + m_phase(phase_unknown), + method_tunnel(mod, inner) +{ +} + + +eap::method_eapmsg::method_eapmsg(_Inout_ method_eapmsg &&other) : + m_identity (std::move(other.m_identity )), + m_phase (std::move(other.m_phase )), + m_packet_res (std::move(other.m_packet_res)), + method_tunnel(std::move(other )) +{ +} + + +eap::method_eapmsg& eap::method_eapmsg::operator=(_Inout_ method_eapmsg &&other) +{ + if (this != std::addressof(other)) { + (method_tunnel&)*this = std::move(other ); + m_identity = std::move(other.m_identity ); + m_phase = std::move(other.m_phase ); + m_packet_res = std::move(other.m_packet_res); + } + + return *this; +} + + +void eap::method_eapmsg::begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_opt_ DWORD dwMaxSendPacketSize) +{ + method_tunnel::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + m_phase = phase_identity; +} + + +EapPeerMethodResponseAction eap::method_eapmsg::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize) +{ + switch (m_phase) { + case phase_identity: { + // Convert identity to UTF-8. + sanitizing_string identity_utf8; + WideCharToMultiByte(CP_UTF8, 0, m_identity.c_str(), -1, identity_utf8, NULL, NULL); + + // Build EAP-Response/Identity packet. + auto size_identity = identity_utf8.length(); + assert(size_identity + 1 <= MAXWORD); // Packets spanning over 64kB are not supported. + eap_packet pck; + if (!pck.create(EapCodeResponse, 0, (WORD)size_identity + 1)) + throw win_runtime_error(__FUNCTION__ " EapPacket creation failed."); + pck->Data[0] = eap_type_identity; + memcpy(pck->Data + 1, identity_utf8.data(), size_identity); + + // Diameter AVP (EAP-Message=79) + m_packet_res.clear(); + diameter_avp_append(79, diameter_avp_flag_mandatory, (const EapPacket*)pck, (unsigned int)size_identity + 5, m_packet_res); + + m_phase = phase_finished; + return EapPeerMethodResponseActionSend; + } + + case phase_finished: { + EapPeerMethodResponseAction action = EapPeerMethodResponseActionNone; + bool eap_message_found = false; + + // Parse Diameter AVP(s). + // Process the first EAP-Message, but keep iterating over all to check if there is any additional mandatory AVP we should process. + for (const unsigned char *pck = reinterpret_cast(pReceivedPacket), *pck_end = pck + dwReceivedPacketSize; pck < pck_end; ) { + if (pck + sizeof(diameter_avp_header) > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message header."); + const diameter_avp_header *hdr = reinterpret_cast(pck); + unsigned int size_msg = ntoh24(hdr->length); + const unsigned char + *msg = reinterpret_cast(hdr + 1), + *msg_end = pck + size_msg, + *msg_next = pck + (size_msg + 3) / 4; + if (msg_end > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message data."); + unsigned int code = ntohl(*reinterpret_cast(hdr->code)); + + switch (code) { + case 79: // EAP-Message + if (!eap_message_found) { + action = method_tunnel::process_request_packet(msg, (DWORD)(msg_end - msg)); + eap_message_found = true; + break; + } + // Do not break out of this case to allow continuing with the following case, checking there is no second mandatory EAP-Message present. + + default: + if (hdr->flags & diameter_avp_flag_mandatory) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " Unsupported mandatory Diameter AVP (code %u).", code)); + } + + pck = msg_next; + } + + // Signal get_response_packet() method we did not generate any response data to proxy inner method. + m_packet_res.clear(); + + return action; + } + + default: + throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase).c_str()); + } +} + + +void eap::method_eapmsg::get_response_packet( + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) +{ + if (m_packet_res.empty()) { + assert(size_max >= sizeof(diameter_avp_header)); // We should be able to respond with at least Diameter AVP header. + + if (size_max > 0xffffff) size_max = 0xffffff; // Diameter AVP maximum size is 16MB. + + // Get data from underlying method. + method_tunnel::get_response_packet(packet, size_max - sizeof(diameter_avp_header)); + + // Prepare EAP-Message Diameter AVP header. + diameter_avp_header hdr; + *reinterpret_cast(hdr.code) = htonl(79); + hdr.flags = diameter_avp_flag_mandatory; + size_t size_packet = packet.size() + sizeof(diameter_avp_header); + assert(size_packet <= 0xffffff); // Packets spanning over 16MB are not supported. + hton24((unsigned int)size_packet, hdr.length); + + // Insert EAP header before data. + packet.insert(packet.begin(), reinterpret_cast(&hdr), reinterpret_cast(&hdr + 1)); + + // Add padding. + packet.resize((size_packet + 3) / 4); + } else { + 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()); + } +} + + ////////////////////////////////////////////////////////////////////// // eap::method_ttls ////////////////////////////////////////////////////////////////////// -eap::method_ttls::method_ttls(_In_ module &module, _In_ config_method_ttls &cfg, _In_ credentials_ttls &cred) : - m_version(version_0), - method_tls(module, cfg, cred) +eap::method_ttls::method_ttls(_In_ module &mod, _In_ config_method_ttls &cfg, _In_ credentials_ttls &cred, _In_ method *inner) : + m_cfg(cfg), + m_cred(cred), + m_user_ctx(NULL), + m_phase(phase_unknown), + m_packet_res_inner(false), + method_tunnel(mod, inner) { } eap::method_ttls::method_ttls(_Inout_ method_ttls &&other) : - m_version (std::move(other.m_version)), - m_inner (std::move(other.m_inner )), - method_tls(std::move(other )) + m_cfg ( other.m_cfg ), + m_cred ( other.m_cred ), + m_user_ctx (std::move(other.m_user_ctx )), + m_sc_target_name (std::move(other.m_sc_target_name )), + m_sc_cred (std::move(other.m_sc_cred )), + m_sc_queue (std::move(other.m_sc_queue )), + m_sc_ctx (std::move(other.m_sc_ctx )), + m_phase (std::move(other.m_phase )), + m_packet_res (std::move(other.m_packet_res )), + m_packet_res_inner(std::move(other.m_packet_res_inner)), + m_eap_attr (std::move(other.m_eap_attr )), + method_tunnel (std::move(other )) { } @@ -46,9 +343,18 @@ eap::method_ttls::method_ttls(_Inout_ method_ttls &&other) : eap::method_ttls& eap::method_ttls::operator=(_Inout_ method_ttls &&other) { if (this != std::addressof(other)) { - (method_tls&)*this = std::move(other ); - m_version = std::move(other.m_version); - m_inner = std::move(other.m_inner ); + assert(std::addressof(m_cfg ) == std::addressof(other.m_cfg )); // Move method within same configuration only! + assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method within same credentials only! + (method_tunnel&)*this = std::move(other ); + m_user_ctx = std::move(other.m_user_ctx ); + m_sc_target_name = std::move(other.m_sc_target_name ); + m_sc_cred = std::move(other.m_sc_cred ); + m_sc_queue = std::move(other.m_sc_queue ); + m_sc_ctx = std::move(other.m_sc_ctx ); + m_phase = std::move(other.m_phase ); + m_packet_res = std::move(other.m_packet_res ); + m_packet_res_inner = std::move(other.m_packet_res_inner); + m_eap_attr = std::move(other.m_eap_attr ); } return *this; @@ -61,31 +367,63 @@ void eap::method_ttls::begin_session( _In_ HANDLE hTokenImpersonateUser, _In_opt_ DWORD dwMaxSendPacketSize) { - method_tls::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + method_tunnel::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); - // Initialize inner method. - auto cfg_inner = dynamic_cast(m_cfg ).m_inner.get(); - auto cred_inner = dynamic_cast(m_cred).m_inner.get(); - auto cfg_inner_eaphost = dynamic_cast(cfg_inner); - if (!cfg_inner_eaphost) { - // Native inner methods - switch (cfg_inner->get_method_id()) { - case eap_type_legacy_pap : m_inner.reset(new method_pap (m_module, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; - case eap_type_legacy_mschapv2: m_inner.reset(new method_mschapv2(m_module, dynamic_cast(*cfg_inner), dynamic_cast(*cred_inner))); break; - default: throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); - } - } else { - // EapHost inner method - m_inner.reset(new method_eaphost(m_module, *cfg_inner_eaphost, dynamic_cast(*cred_inner))); + // Presume authentication will fail with generic protocol failure. (Pesimist!!!) + // We will reset once we get get_result(Success) call. + m_cfg.m_last_status = config_method::status_auth_failed; + m_cfg.m_last_msg.clear(); + + m_user_ctx = hTokenImpersonateUser; + user_impersonator impersonating(m_user_ctx); + + // Build (expected) server name(s) for Schannel. + m_sc_target_name.clear(); + for (auto name = m_cfg.m_server_names.cbegin(), name_end = m_cfg.m_server_names.cend(); name != name_end; ++name) { + if (name != m_cfg.m_server_names.cbegin()) + m_sc_target_name += _T(';'); +#ifdef _UNICODE + m_sc_target_name.insert(m_sc_target_name.end(), name->begin(), name->end()); +#else + string buf; + WideCharToMultiByte(CP_ACP, 0, name->c_str(), -1, buf, NULL, NULL); + m_sc_target_name.insert(m_sc_target_name.end(), buf.begin(), buf.end()); +#endif } - m_inner->begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, MAXDWORD); -} + // Prepare client credentials for Schannel. + PCCERT_CONTEXT certs[] = { m_cred.m_cert ? (PCCERT_CONTEXT)m_cred.m_cert : NULL }; + SCHANNEL_CRED cred = { + SCHANNEL_CRED_VERSION, // dwVersion + m_cred.m_cert ? 1 : 0, // cCreds + certs, // paCred + NULL, // hRootStore: Not valid for client credentials + 0, // cMappers + NULL, // aphMappers + 0, // cSupportedAlgs: Use system configured default + NULL, // palgSupportedAlgs: Use system configured default + SP_PROT_TLS1_X_CLIENT | (SP_PROT_TLS1_2_CLIENT<<2), // grbitEnabledProtocols: TLS 1.x + 0, // dwMinimumCipherStrength: Use system configured default + 0, // dwMaximumCipherStrength: Use system configured default + 0, // dwSessionLifespan: Use system configured default = 10hr +#if EAP_TLS >= EAP_TLS_SCHANNEL_FULL + SCH_CRED_AUTO_CRED_VALIDATION | // dwFlags: Let Schannel verify server certificate +#else + SCH_CRED_MANUAL_CRED_VALIDATION | // dwFlags: Prevent Schannel verify server certificate (we want to use custom root CA store and multiple name checking) +#endif + SCH_CRED_CACHE_ONLY_URL_RETRIEVAL_ON_CREATE | // dwFlags: Do not attempt online revocation check - we do not expect to have network connection yet + SCH_CRED_IGNORE_NO_REVOCATION_CHECK | // dwFlags: Ignore no-revocation-check errors - as we cannot check for revocation, it makes little sense to insist certificate has to have revocation set-up + SCH_CRED_IGNORE_REVOCATION_OFFLINE | // dwFlags: Ignore offline-revocation errors - we do not expect to have network connection yet + SCH_CRED_NO_DEFAULT_CREDS | // dwFlags: If client certificate we provided is not acceptable, do not try to select one on your own + (m_cfg.m_server_names.empty() ? SCH_CRED_NO_SERVERNAME_CHECK : 0) | // dwFlags: When no expected server name is given, do not do the server name check. + 0x00400000 /*SCH_USE_STRONG_CRYPTO*/, // dwFlags: Do not use broken ciphers + 0 // dwCredFormat + }; + SECURITY_STATUS stat = m_sc_cred.acquire(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred); + if (FAILED(stat)) + throw sec_runtime_error(stat, __FUNCTION__ " Error acquiring Schannel credentials handle."); -void eap::method_ttls::end_session() -{ - m_inner->end_session(); - method_tls::end_session(); + m_phase = phase_handshake_init; } @@ -93,30 +431,313 @@ EapPeerMethodResponseAction eap::method_ttls::process_request_packet( _In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket, _In_ DWORD dwReceivedPacketSize) { - if (((const EapPacket*)pReceivedPacket)->Code == EapCodeRequest && (((const EapPacket*)pReceivedPacket)->Data[1] & packet_ttls::flags_start)) { - // This is a start EAP-TTLS packet. + assert(pReceivedPacket || dwReceivedPacketSize == 0); - // Determine minimum EAP-TTLS version supported by server and us. - version_t ver_remote = (version_t)(((const EapPacket*)pReceivedPacket)->Data[1] & packet_ttls::flags_ver_mask); - m_version = std::min(ver_remote, version_0); - m_module.log_event(&EAPMETHOD_TTLS_HANDSHAKE_START, event_data((unsigned int)eap_type_ttls), event_data((unsigned char)m_version), event_data((unsigned char)ver_remote), event_data::blank); + user_impersonator impersonating(m_user_ctx); + + switch (m_phase) { + case phase_handshake_init: { + // Prepare input buffer(s). + SecBuffer buf_in[] = { + { (unsigned long)dwReceivedPacketSize, SECBUFFER_TOKEN, const_cast(pReceivedPacket) }, + { 0, SECBUFFER_EMPTY, NULL }, + }; + SecBufferDesc buf_in_desc = { SECBUFFER_VERSION, _countof(buf_in), buf_in }; + + // Prepare output buffer(s). + SecBuffer buf_out[] = { + { 0, SECBUFFER_TOKEN, NULL }, + { 0, SECBUFFER_ALERT, NULL }, + }; + sec_buffer_desc buf_out_desc(buf_out, _countof(buf_out)); + + // Initialize Schannel security context and process initial data. + SECURITY_STATUS status = m_sc_ctx.initialize( + m_sc_cred, + !m_sc_target_name.empty() ? m_sc_target_name.c_str() : NULL, + ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | ISC_REQ_STREAM | /*ISC_REQ_USE_SUPPLIED_CREDS |*/ ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY, + 0, + &buf_in_desc, + &buf_out_desc); + + // In a desparate attempt to make Schannel remember and resume the TLS session, we send it the SCHANNEL_SESSION_TOKEN/SSL_SESSION_ENABLE_RECONNECTS + SCHANNEL_SESSION_TOKEN token_session = { SCHANNEL_SESSION, SSL_SESSION_ENABLE_RECONNECTS }; + SecBuffer token[] = { { sizeof(token_session), SECBUFFER_TOKEN, &token_session } }; + SecBufferDesc token_desc = { SECBUFFER_VERSION, _countof(token), token }; + ApplyControlToken(m_sc_ctx, &token_desc); + + if (status == SEC_I_CONTINUE_NEEDED) { + // Send Schannel's token. + assert(buf_out[0].BufferType == SECBUFFER_TOKEN); + assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); + m_packet_res.assign(reinterpret_cast(buf_out[0].pvBuffer), reinterpret_cast(buf_out[0].pvBuffer) + buf_out[0].cbBuffer); + if (buf_in[1].BufferType == SECBUFFER_EXTRA) { + // Server appended extra data. + m_sc_queue.assign(reinterpret_cast(pReceivedPacket) + dwReceivedPacketSize - buf_in[1].cbBuffer, reinterpret_cast(pReceivedPacket) + dwReceivedPacketSize); + } else + m_sc_queue.clear(); + + m_phase = phase_handshake_cont; + m_packet_res_inner = false; + return EapPeerMethodResponseActionSend; + } else if (FAILED(status)) { + if (m_sc_ctx.m_attrib & ISC_RET_EXTENDED_ERROR) { + // Send alert. + assert(buf_out[1].BufferType == SECBUFFER_ALERT); + assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); + m_packet_res.assign(reinterpret_cast(buf_out[1].pvBuffer), reinterpret_cast(buf_out[1].pvBuffer) + buf_out[1].cbBuffer); + m_packet_res_inner = false; + return EapPeerMethodResponseActionSend; + } else + throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); + } else + throw sec_runtime_error(status, __FUNCTION__ " Unexpected Schannel result."); } - // Do the TLS. - return method_tls::process_request_packet(pReceivedPacket, dwReceivedPacketSize); + case phase_handshake_cont: { + m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast(pReceivedPacket), reinterpret_cast(pReceivedPacket) + dwReceivedPacketSize); + + // Prepare input buffer(s). + SecBuffer buf_in[] = { + { (unsigned long)m_sc_queue.size(), SECBUFFER_TOKEN, m_sc_queue.data() }, + { 0, SECBUFFER_EMPTY, NULL }, + }; + SecBufferDesc buf_in_desc = { SECBUFFER_VERSION, _countof(buf_in), buf_in }; + + // Prepare output buffer(s). + SecBuffer buf_out[] = { + { 0, SECBUFFER_TOKEN, NULL }, + { 0, SECBUFFER_ALERT, NULL }, + }; + sec_buffer_desc buf_out_desc(buf_out, _countof(buf_out)); + + // Process Schannel data. + SECURITY_STATUS status = m_sc_ctx.process( + m_sc_cred, + !m_sc_target_name.empty() ? m_sc_target_name.c_str() : NULL, + ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | ISC_REQ_STREAM | /*ISC_REQ_USE_SUPPLIED_CREDS |*/ ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY, + 0, + &buf_in_desc, + &buf_out_desc); + +#if EAP_TLS < EAP_TLS_SCHANNEL_FULL + if (status == SEC_E_OK) + verify_server_trust(); +#endif + + if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) { + // Send Schannel's token. + assert(buf_out[0].BufferType == SECBUFFER_TOKEN); + assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); + m_packet_res.assign(reinterpret_cast(buf_out[0].pvBuffer), reinterpret_cast(buf_out[0].pvBuffer) + buf_out[0].cbBuffer); + if (buf_in[1].BufferType == SECBUFFER_EXTRA) { + // Server appended extra data. + m_sc_queue.erase(m_sc_queue.begin(), m_sc_queue.end() - buf_in[1].cbBuffer); + } else + m_sc_queue.clear(); + + if (status == SEC_I_CONTINUE_NEEDED) { + // Blame credentials if we fail beyond this point. + m_cfg.m_last_status = config_method::status_cred_invalid; + m_packet_res_inner = false; + } else { + SecPkgContext_Authority auth; + if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_AUTHORITY, &auth))) { + m_module.log_event(&EAPMETHOD_TLS_QUERY_FAILED, event_data((unsigned int)SECPKG_ATTR_AUTHORITY), event_data(status), event_data::blank); + auth.sAuthorityName = _T(""); + } + + SecPkgContext_ConnectionInfo info; + if (SUCCEEDED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_CONNECTION_INFO, &info))) + m_module.log_event(&EAPMETHOD_TLS_HANDSHAKE_FINISHED, + event_data((unsigned int)eap_type_tls), + event_data(auth.sAuthorityName), + event_data(info.dwProtocol), + event_data(info.aiCipher), + event_data(info.dwCipherStrength), + event_data(info.aiHash), + event_data(info.dwHashStrength), + event_data(info.aiExch), + event_data(info.dwExchStrength), + event_data::blank); + else + m_module.log_event(&EAPMETHOD_TLS_QUERY_FAILED, event_data((unsigned int)SECPKG_ATTR_CONNECTION_INFO), event_data(status), event_data::blank); + + m_phase = phase_finished; + + method_mschapv2 *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 + static const SecPkgContext_EapPrfInfo s_prf_info = { 0, sizeof(s_key_id), (PBYTE)&s_key_id }; + SECURITY_STATUS status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&s_prf_info, sizeof(s_prf_info)); + if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Error setting TTLS PRF in Schannel."); + + SecPkgContext_EapKeyBlock key_block; + status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block); + if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Error generating PRF in Schannel."); + + memcpy(&inner_mschapv2->m_challenge_server, key_block.rgbKeys, sizeof(challenge_mschapv2)); + inner_mschapv2->m_ident = key_block.rgbKeys[sizeof(challenge_mschapv2) + 0]; + + SecureZeroMemory(&key_block, sizeof(key_block)); + } + + // Piggyback initial inner response. + if (!(m_sc_ctx.m_attrib & ISC_RET_CONFIDENTIALITY)) + throw runtime_error(__FUNCTION__ " Connection is not encrypted."); + + EapPeerMethodResponseAction action = EapPeerMethodResponseActionDiscard; + if (m_sc_queue.empty()) { + // No extra initial data for inner authentication avaliable. + action = method_tunnel::process_request_packet(NULL, 0); + } else { + // Authenticator sent some data for inner authentication. Unencrypt it. + + // Decrypt the message. + SecBuffer buf[] = { + { (unsigned long)m_sc_queue.size(), SECBUFFER_DATA, m_sc_queue.data() }, + { 0, SECBUFFER_EMPTY, NULL }, + { 0, SECBUFFER_EMPTY, NULL }, + { 0, SECBUFFER_EMPTY, NULL }, + }; + SecBufferDesc buf_desc = { SECBUFFER_VERSION, _countof(buf), buf }; + SECURITY_STATUS status = DecryptMessage(m_sc_ctx, &buf_desc, 0, NULL); + if (status == SEC_E_OK) { + // Process data (only the first SECBUFFER_DATA found). + for (size_t i = 0; i < _countof(buf); i++) + if (buf[i].BufferType == SECBUFFER_DATA) { + action = method_tunnel::process_request_packet(buf[i].pvBuffer, buf[i].cbBuffer); + break; + } + + // Queue remaining data for the next time. + m_sc_queue.clear(); + for (size_t i = 0; i < _countof(buf); i++) + if (buf[i].BufferType == SECBUFFER_EXTRA) + m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast(buf[i].pvBuffer), reinterpret_cast(buf[i].pvBuffer) + buf[i].cbBuffer); + } else if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); + else + throw sec_runtime_error(status, __FUNCTION__ " Unexpected Schannel result."); + } + switch (action) { + case EapPeerMethodResponseActionSend: m_packet_res_inner = true ; break; + case EapPeerMethodResponseActionNone: m_packet_res_inner = false; break; + default : throw invalid_argument(string_printf(__FUNCTION__ " Inner method returned an unsupported action (action %u).", action).c_str()); + } + } + return EapPeerMethodResponseActionSend; + } else if (FAILED(status)) { + if (m_sc_ctx.m_attrib & ISC_RET_EXTENDED_ERROR) { + // Send alert. + assert(buf_out[1].BufferType == SECBUFFER_ALERT); + assert(m_sc_ctx.m_attrib & ISC_RET_ALLOCATED_MEMORY); + m_packet_res.assign(reinterpret_cast(buf_out[1].pvBuffer), reinterpret_cast(buf_out[1].pvBuffer) + buf_out[1].cbBuffer); + m_packet_res_inner = false; + return EapPeerMethodResponseActionSend; + } else + throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); + } else + throw sec_runtime_error(status, __FUNCTION__ " Unexpected Schannel result."); + } + + case phase_finished: { + m_packet_res.clear(); + m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast(pReceivedPacket), reinterpret_cast(pReceivedPacket) + dwReceivedPacketSize); + + if (!(m_sc_ctx.m_attrib & ISC_RET_CONFIDENTIALITY)) + throw runtime_error(__FUNCTION__ " Connection is not encrypted."); + + if (m_sc_queue.empty()) { + // No extra data for inner authentication. + return method_tunnel::process_request_packet(NULL, 0); + } else { + // Authenticator sent data for inner authentication. Unencrypt it. + + // Decrypt the message. + SecBuffer buf[] = { + { (unsigned long)m_sc_queue.size(), SECBUFFER_DATA, m_sc_queue.data() }, + { 0, SECBUFFER_EMPTY, NULL }, + { 0, SECBUFFER_EMPTY, NULL }, + { 0, SECBUFFER_EMPTY, NULL }, + }; + SecBufferDesc buf_desc = { SECBUFFER_VERSION, _countof(buf), buf }; + SECURITY_STATUS status = DecryptMessage(m_sc_ctx, &buf_desc, 0, NULL); + if (status == SEC_E_OK) { + // Process data (only the first SECBUFFER_DATA found). + EapPeerMethodResponseAction action = EapPeerMethodResponseActionDiscard; + for (size_t i = 0; i < _countof(buf); i++) + if (buf[i].BufferType == SECBUFFER_DATA) { + action = method_tunnel::process_request_packet(buf[i].pvBuffer, buf[i].cbBuffer); + break; + } + + // Queue remaining data for the next time. + m_sc_queue.clear(); + for (size_t i = 0; i < _countof(buf); i++) + if (buf[i].BufferType == SECBUFFER_EXTRA) + m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast(buf[i].pvBuffer), reinterpret_cast(buf[i].pvBuffer) + buf[i].cbBuffer); + + return action; + } else if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Schannel error."); + else + throw sec_runtime_error(status, __FUNCTION__ " Unexpected Schannel result."); + } + } + + default: + throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase).c_str()); + } } void eap::method_ttls::get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) void *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) + _Out_ sanitizing_blob &packet, + _In_opt_ DWORD size_max) { - method_tls::get_response_packet(pSendPacket, pdwSendPacketSize); + if (m_packet_res_inner) { + // Get maximum allowable packet size. + SecPkgContext_StreamSizes sizes; + SECURITY_STATUS status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_STREAM_SIZES, &sizes); + if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Error getting Schannel required encryption sizes."); + if (m_packet_res.size() + sizes.cbHeader + sizes.cbTrailer > 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()); + sizes.cbMaximumMessage = std::min(sizes.cbMaximumMessage, size_max - (unsigned long)(m_packet_res.size() + sizes.cbHeader + sizes.cbTrailer)); - // Change packet type to EAP-TTLS, and add EAP-TTLS version. - ((EapPacket*)pSendPacket)->Data[0] = (BYTE)eap_type_ttls; - ((EapPacket*)pSendPacket)->Data[1] &= ~packet_ttls::flags_ver_mask; - ((EapPacket*)pSendPacket)->Data[1] |= m_version; + // Get inner response packet. + packet.reserve(sizes.cbHeader + sizes.cbMaximumMessage + sizes.cbTrailer); + method_tunnel::get_response_packet(packet, sizes.cbMaximumMessage); + if (!packet.empty()) { + DWORD size_data = (DWORD)packet.size(); + + // Insert and append space for header and trailer. + packet.insert(packet.begin(), sizes.cbHeader , 0); + packet.insert(packet.end (), sizes.cbTrailer, 0); + + // Encrypt the message. + unsigned char *ptr_data = packet.data(); + SecBuffer buf[] = { + { sizes.cbHeader, SECBUFFER_STREAM_HEADER , ptr_data }, + { size_data, SECBUFFER_DATA , ptr_data += sizes.cbHeader }, + { sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, ptr_data += size_data }, + { 0, SECBUFFER_EMPTY , NULL }, + }; + SecBufferDesc buf_desc = { SECBUFFER_VERSION, _countof(buf), buf }; + status = EncryptMessage(m_sc_ctx, 0, &buf_desc, 0); + if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Error encrypting message."); + + m_packet_res.insert(m_packet_res.end(), reinterpret_cast(buf[0].pvBuffer), reinterpret_cast(buf[0].pvBuffer) + buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer); + } + } else 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()); } @@ -124,233 +745,253 @@ void eap::method_ttls::get_result( _In_ EapPeerMethodResultReason reason, _Inout_ EapPeerMethodResult *pResult) { - // Do the TLS. - method_tls::get_result(reason, pResult); + assert(pResult); - if (m_phase == phase_application_data) { - // Get inner method result. - EapPeerMethodResult result = {}; - m_inner->get_result(reason, &result); - if (result.fSaveConnectionData) - pResult->fSaveConnectionData = TRUE; + // Get inner result. + method_tunnel::get_result(reason, pResult); - if (m_inner->m_cfg.m_last_status != config_method::status_success) { - // Inner method admitted problems, so autentication must have proceeded to inner authentication already. - // Therefore, outer authentication must have been OK. - m_cfg.m_last_status = config_method::status_success; + if (reason == EapPeerMethodResultSuccess) { + eap_attr a; + + // Prepare EAP result attributes. + if (pResult->pAttribArray) { + m_eap_attr.reserve(pResult->pAttribArray->dwNumberOfAttributes + 3); + m_eap_attr.clear(); + // Copy all EAP attributes from inner method up to blank terminator. Exclude any MPPE-Recv-Key or MPPE-Send-Key if found. + for (auto attr = pResult->pAttribArray->pAttribs, attr_end = pResult->pAttribArray->pAttribs + pResult->pAttribArray->dwNumberOfAttributes; attr != attr_end && attr->eaType; ++attr) { + if (attr->eaType != eatVendorSpecific || attr->dwLength < 5 || ntohl(*reinterpret_cast(attr->pValue)) != 311 || attr->pValue[4] != 16 && attr->pValue[4] != 17) + m_eap_attr.push_back(*attr); + } + } else { + m_eap_attr.reserve(3); + m_eap_attr.clear(); } - } -} - -void eap::method_ttls::get_ui_context( - _Inout_ BYTE **ppUIContextData, - _Inout_ DWORD *pdwUIContextDataSize) -{ - UNREFERENCED_PARAMETER(ppUIContextData); - UNREFERENCED_PARAMETER(pdwUIContextDataSize); - - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); -} - - -EapPeerMethodResponseAction eap::method_ttls::set_ui_context( - _In_count_(dwUIContextDataSize) const BYTE *pUIContextData, - _In_ DWORD dwUIContextDataSize) -{ - UNREFERENCED_PARAMETER(pUIContextData); - UNREFERENCED_PARAMETER(dwUIContextDataSize); - - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); -} - - -void eap::method_ttls::get_response_attributes(_Inout_ EapAttributes *pAttribs) -{ - UNREFERENCED_PARAMETER(pAttribs); - - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); -} - - -EapPeerMethodResponseAction eap::method_ttls::set_response_attributes(_In_ const EapAttributes *pAttribs) -{ - UNREFERENCED_PARAMETER(pAttribs); - - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); -} - - -void eap::method_ttls::derive_msk() -{ - const unsigned char *_key_block; - -#if EAP_TLS < EAP_TLS_SCHANNEL - // - // TLS versions 1.0 [RFC2246] and 1.1 [RFC4346] define the same PRF - // function, and any EAP-TTLSv0 implementation based on these versions - // of TLS must use the PRF defined therein. It is expected that future - // versions of or extensions to the TLS protocol will permit alternative - // PRF functions to be negotiated. If an alternative PRF function is - // specified for the underlying TLS version or has been negotiated - // during the TLS handshake negotiation, then that alternative PRF - // function must be used in EAP-TTLSv0 computations instead of the TLS - // 1.0/1.1 PRF. - // - // [Extensible Authentication Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0 (EAP-TTLSv0) (Chapter 7.8. Use of TLS PRF)](https://tools.ietf.org/html/rfc5281#section-7.8) - // - // If we use PRF_SHA256() the key exchange fails. Therefore we use PRF of TLS 1.0/1.1. - // - static const unsigned char s_label[] = "ttls keying material"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - sanitizing_blob key_block(prf(m_cp, CALG_TLS1PRF, m_master_secret, seed, 2*sizeof(tls_random))); - _key_block = key_block.data(); -#else - // EAP-TTLS uses different label in PRF for MSK derivation than EAP-TLS. - static const DWORD s_key_id = 0x01; // EAP-TTLSv0 Keying Material - static const SecPkgContext_EapPrfInfo s_prf_info = { 0, sizeof(s_key_id), (PBYTE)&s_key_id }; - SECURITY_STATUS status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&s_prf_info, sizeof(s_prf_info)); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error setting EAP-TTLS PRF in Schannel."); - - SecPkgContext_EapKeyBlock key_block; - status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error generating MSK in Schannel."); - _key_block = key_block.rgbKeys; -#endif - - // MSK: MPPE-Recv-Key - memcpy(&m_key_mppe_client, _key_block, sizeof(tls_random)); - _key_block += sizeof(tls_random); - - // MSK: MPPE-Send-Key - memcpy(&m_key_mppe_server, _key_block, sizeof(tls_random)); - _key_block += sizeof(tls_random); - -#if EAP_TLS >= EAP_TLS_SCHANNEL - SecureZeroMemory(&key_block, sizeof(key_block)); -#endif -} - - -void eap::method_ttls::derive_challenge() -{ - method_mschapv2 *inner_mschapv2 = dynamic_cast(m_inner.get()); - if (inner_mschapv2) { -#if EAP_TLS < EAP_TLS_SCHANNEL - static const unsigned char s_label[] = "ttls challenge"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), reinterpret_cast(&m_random_client), reinterpret_cast(&m_random_client + 1)); - seed.insert(seed.end(), reinterpret_cast(&m_random_server), reinterpret_cast(&m_random_server + 1)); - sanitizing_blob keying(prf(m_cp, CALG_TLS1PRF, m_master_secret, seed, sizeof(challenge_mschapv2) + 1)); - memcpy(&inner_mschapv2->m_challenge_server, keying.data(), sizeof(challenge_mschapv2)); - inner_mschapv2->m_ident = keying[sizeof(challenge_mschapv2) + 0]; -#else - static const DWORD s_key_id = 0x02; // EAP-TTLSv0 Challenge Data + // Derive MSK keys. + static const DWORD s_key_id = 0x01; // EAP-TTLSv0 Keying Material static const SecPkgContext_EapPrfInfo s_prf_info = { 0, sizeof(s_key_id), (PBYTE)&s_key_id }; SECURITY_STATUS status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&s_prf_info, sizeof(s_prf_info)); if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error setting EAP-TTLS PRF in Schannel."); + throw sec_runtime_error(status, __FUNCTION__ " Error setting TTLS PRF in Schannel."); SecPkgContext_EapKeyBlock key_block; status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block); if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error generating PRF in Schannel."); + throw sec_runtime_error(status, __FUNCTION__ " Error generating MSK in Schannel."); + const unsigned char *_key_block = key_block.rgbKeys; - memcpy(&inner_mschapv2->m_challenge_server, key_block.rgbKeys, sizeof(challenge_mschapv2)); - inner_mschapv2->m_ident = key_block.rgbKeys[sizeof(challenge_mschapv2) + 0]; -#endif + // MSK: MPPE-Recv-Key + a.create_ms_mppe_key(16, _key_block, sizeof(tls_random)); + m_eap_attr.push_back(std::move(a)); + _key_block += sizeof(tls_random); + + // MSK: MPPE-Send-Key + a.create_ms_mppe_key(17, _key_block, sizeof(tls_random)); + m_eap_attr.push_back(std::move(a)); + _key_block += sizeof(tls_random); + + SecureZeroMemory(&key_block, sizeof(key_block)); + + // Append blank EAP attribute. + m_eap_attr.push_back(eap_attr::blank); + + m_eap_attr_desc.dwNumberOfAttributes = (DWORD)m_eap_attr.size(); + m_eap_attr_desc.pAttribs = m_eap_attr.data(); + pResult->pAttribArray = &m_eap_attr_desc; + + 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; + + //if (m_phase == phase_finished) { + // // Get inner method result. + // EapPeerMethodResult result = {}; + // m_inner->get_result(reason, &result); + // if (result.fSaveConnectionData) + // pResult->fSaveConnectionData = TRUE; + + // if (m_inner->m_cfg.m_last_status != config_method::status_success) { + // // Inner method admitted problems, so autentication must have proceeded to inner authentication already. + // // Therefore, outer authentication must have been OK. + // m_cfg.m_last_status = config_method::status_success; + // } + //} } -EapPeerMethodResponseAction eap::method_ttls::process_application_data(_In_bytecount_(size_msg) const void *msg, _In_ size_t size_msg) +#if EAP_TLS < EAP_TLS_SCHANNEL_FULL + +void eap::method_ttls::verify_server_trust() const { - // Prepare inner authentication. -#if EAP_TLS < EAP_TLS_SCHANNEL - if (!m_state_client.m_alg_encrypt) - throw runtime_error(__FUNCTION__ " Refusing to continue with inner authentication unencrypted."); + cert_context cert; + SECURITY_STATUS status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&cert); + if (FAILED(status)) + throw sec_runtime_error(status, __FUNCTION__ " Error retrieving server certificate from Schannel."); - if (m_session_resumed) { - // On reconnect we do not need to do inner re-authentication. - return EapPeerMethodResponseActionNone; + for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) { + if (cert->cbCertEncoded == (*c)->cbCertEncoded && + memcmp(cert->pbCertEncoded, (*c)->pbCertEncoded, cert->cbCertEncoded) == 0) + { + // Server certificate found directly on the trusted root CA list. + m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED_EX, event_data::blank); + return; + } } -#else - if (!(m_sc_ctx.m_attrib & ISC_RET_CONFIDENTIALITY)) - throw runtime_error(__FUNCTION__ " Refusing to continue with inner authentication unencrypted."); - SecPkgContext_SessionInfo session_info; - if (SUCCEEDED(QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_SESSION_INFO, &session_info)) && (session_info.dwFlags & SSL_SESSION_RECONNECT)) { - // On reconnect we do not need to do inner re-authentication. - // According to MSDN QueryContextAttributes(SECPKG_ATTR_SESSION_INFO) works from Windows 7 on. Therefore behaviour might vary. - return EapPeerMethodResponseActionNone; - } -#endif + // Check server name. + if (!m_cfg.m_server_names.empty()) { + bool + has_san = false, + found = false; - EapPeerMethodResponseAction action = m_inner->process_request_packet(msg, (DWORD)size_msg); - switch (action) { - case EapPeerMethodResponseActionSend: { - // Retrieve inner packet and send it. + // Search subjectAltName2 and subjectAltName. + for (DWORD idx_ext = 0; !found && idx_ext < cert->pCertInfo->cExtension; idx_ext++) { + unique_ptr > san_info; + if (strcmp(cert->pCertInfo->rgExtension[idx_ext].pszObjId, szOID_SUBJECT_ALT_NAME2) == 0) { + unsigned char *output = NULL; + DWORD size_output; + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + cert->pCertInfo->rgExtension[idx_ext].Value.pbData, cert->pCertInfo->rgExtension[idx_ext].Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_ENABLE_PUNYCODE_FLAG, + NULL, + &output, &size_output)) + throw win_runtime_error(__FUNCTION__ " Error decoding subjectAltName2 certificate extension."); + san_info.reset((CERT_ALT_NAME_INFO*)output); + } else if (strcmp(cert->pCertInfo->rgExtension[idx_ext].pszObjId, szOID_SUBJECT_ALT_NAME) == 0) { + unsigned char *output = NULL; + DWORD size_output; + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME, + cert->pCertInfo->rgExtension[idx_ext].Value.pbData, cert->pCertInfo->rgExtension[idx_ext].Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_ENABLE_PUNYCODE_FLAG, + NULL, + &output, &size_output)) + throw win_runtime_error(__FUNCTION__ " Error decoding subjectAltName certificate extension."); + san_info.reset((CERT_ALT_NAME_INFO*)output); + } else { + // Skip this extension. + continue; + } + has_san = true; - // Get maximum message size and allocate memory for response packet. -#if EAP_TLS < EAP_TLS_SCHANNEL - m_packet_res.m_code = EapCodeResponse; - m_packet_res.m_id = m_packet_req.m_id; - m_packet_res.m_flags = 0; - - DWORD size_data = (DWORD)get_max_message(16384 - sizeof(message_header)); - sanitizing_blob data(size_data, 0); - unsigned char *ptr_data = data.data(); -#else - SecPkgContext_StreamSizes sizes; - SECURITY_STATUS status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_STREAM_SIZES, &sizes); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error getting Schannel required encryption sizes."); - - sanitizing_blob data(sizes.cbHeader + sizes.cbMaximumMessage + sizes.cbTrailer, 0); - DWORD size_data = sizes.cbMaximumMessage; - unsigned char *ptr_data = data.data() + sizes.cbHeader; -#endif - m_inner->get_response_packet(ptr_data, &size_data); - - if (size_data) { -#if EAP_TLS < EAP_TLS_SCHANNEL - data.resize(size_data); - sanitizing_blob msg_application(make_message(tls_message_type_application_data, std::move(data))); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), msg_application.begin(), msg_application.end()); -#else - // Prepare input/output buffer(s). - SecBuffer buf[] = { - { sizes.cbHeader, SECBUFFER_STREAM_HEADER , data.data() }, - { size_data, SECBUFFER_DATA , ptr_data }, - { sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, ptr_data + size_data }, - { 0, SECBUFFER_EMPTY , NULL }, - }; - SecBufferDesc buf_desc = { - SECBUFFER_VERSION, - _countof(buf), - buf - }; - - // Encrypt the message. - status = EncryptMessage(m_sc_ctx, 0, &buf_desc, 0); - if (FAILED(status)) - throw sec_runtime_error(status, __FUNCTION__ " Error encrypting message."); - m_packet_res.m_data.insert(m_packet_res.m_data.end(), reinterpret_cast(buf[0].pvBuffer), reinterpret_cast(buf[0].pvBuffer) + buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer); -#endif - } else { - // Empty packets represent ACK message, and are not encrypted. + for (auto s = m_cfg.m_server_names.cbegin(), s_end = m_cfg.m_server_names.cend(); !found && s != s_end; ++s) { + for (DWORD idx_entry = 0; !found && idx_entry < san_info->cAltEntry; idx_entry++) { + if (san_info->rgAltEntry[idx_entry].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME && + _wcsicmp(s->c_str(), san_info->rgAltEntry[idx_entry].pwszDNSName) == 0) + { + m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED1, event_data(san_info->rgAltEntry[idx_entry].pwszDNSName), event_data::blank); + found = true; + } + } + } } - break; + if (!has_san) { + // Certificate has no subjectAltName. Compare against Common Name. + wstring subj; + if (!CertGetNameStringW(cert, CERT_NAME_DNS_TYPE, CERT_NAME_STR_ENABLE_PUNYCODE_FLAG, NULL, subj)) + throw win_runtime_error(__FUNCTION__ " Error retrieving server's certificate subject name."); + + for (auto s = m_cfg.m_server_names.cbegin(), s_end = m_cfg.m_server_names.cend(); !found && s != s_end; ++s) { + if (_wcsicmp(s->c_str(), subj.c_str()) == 0) { + m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED1, event_data(subj), event_data::blank); + found = true; + } + } + } + + if (!found) + throw sec_runtime_error(SEC_E_WRONG_PRINCIPAL, __FUNCTION__ " Name provided in server certificate is not on the list of trusted server names."); } - default: - throw invalid_argument(string_printf(__FUNCTION__ " Inner method returned an unsupported action (action %u).", action).c_str()); + if (cert->pCertInfo->Issuer.cbData == cert->pCertInfo->Subject.cbData && + memcmp(cert->pCertInfo->Issuer.pbData, cert->pCertInfo->Subject.pbData, cert->pCertInfo->Issuer.cbData) == 0) + throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Server is using a self-signed certificate. Cannot trust it."); + + // Create temporary certificate store of our trusted root CAs. + cert_store store; + if (!store.create(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, NULL)) + throw win_runtime_error(__FUNCTION__ " Error creating temporary certificate store."); + for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) + CertAddCertificateContextToStore(store, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); + + // Add all intermediate certificates from the server's certificate chain. + for (cert_context c(cert); c;) { + DWORD flags = 0; + c.attach(CertGetIssuerCertificateFromStore(cert->hCertStore, c, NULL, &flags)); + if (!c) break; + + if (c->pCertInfo->Issuer.cbData == c->pCertInfo->Subject.cbData && + memcmp(c->pCertInfo->Issuer.pbData, c->pCertInfo->Subject.pbData, c->pCertInfo->Issuer.cbData) == 0) + { + // Skip the root CA certificates (self-signed). We define in whom we trust! + continue; + } + + CertAddCertificateContextToStore(store, c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); } - return action; + // Prepare the certificate chain validation, and check. + CERT_CHAIN_PARA chain_params = { + sizeof(chain_params), // cbSize + { + USAGE_MATCH_TYPE_AND, // RequestedUsage.dwType + {}, // RequestedUsage.Usage + }, +#ifdef CERT_CHAIN_PARA_HAS_EXTRA_FIELDS + {}, // RequestedIssuancePolicy + 1, // dwUrlRetrievalTimeout (1ms to speed up checking) +#else +#define _S2(x) #x +#define _S(x) _S2(x) +#pragma message(__FILE__ "(" _S(__LINE__) "): warning X0000: Please define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS constant when compiling this project.") +#endif + }; + cert_chain_context context; + if (!context.create(NULL, cert, NULL, store, &chain_params, 0)) + throw win_runtime_error(__FUNCTION__ " Error creating certificate chain context."); + + // Check chain validation error flags. Ignore CERT_TRUST_IS_UNTRUSTED_ROOT flag since we check root CA explicitly. + if (context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR && + (context->TrustStatus.dwErrorStatus & ~CERT_TRUST_IS_UNTRUSTED_ROOT) != CERT_TRUST_NO_ERROR) + { + if (context->TrustStatus.dwErrorStatus & (CERT_TRUST_IS_NOT_TIME_VALID | CERT_TRUST_IS_NOT_TIME_NESTED)) + throw sec_runtime_error(SEC_E_CERT_EXPIRED, __FUNCTION__ " Server certificate has expired (or is not valid yet)."); + else if (context->TrustStatus.dwErrorStatus & (CERT_TRUST_IS_UNTRUSTED_ROOT | CERT_TRUST_IS_PARTIAL_CHAIN)) + throw sec_runtime_error(SEC_E_UNTRUSTED_ROOT, __FUNCTION__ " Server's certificate not issued by one of configured trusted root CAs."); + else + throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Error validating server certificate."); + } + + // Verify Root CA against our trusted root CA list + if (context->cChain != 1) + throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Multiple chain verification not supported."); + if (context->rgpChain[0]->cElement == 0) + throw sec_runtime_error(SEC_E_CERT_UNKNOWN, __FUNCTION__ " Can not verify empty certificate chain."); + + PCCERT_CONTEXT cert_root = context->rgpChain[0]->rgpElement[context->rgpChain[0]->cElement-1]->pCertContext; + for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend();; ++c) { + if (c != c_end) { + if (cert_root->cbCertEncoded == (*c)->cbCertEncoded && + memcmp(cert_root->pbCertEncoded, (*c)->pbCertEncoded, cert_root->cbCertEncoded) == 0) + { + // Trusted root CA found. + break; + } + } else { + // Not found. + throw sec_runtime_error(SEC_E_UNTRUSTED_ROOT, __FUNCTION__ " Server's certificate not issued by one of configured trusted root CAs."); + } + } + + m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED, event_data::blank); } + +#endif diff --git a/lib/TTLS/src/Module.cpp b/lib/TTLS/src/Module.cpp index d5045a8..120bd9f 100644 --- a/lib/TTLS/src/Module.cpp +++ b/lib/TTLS/src/Module.cpp @@ -227,7 +227,27 @@ EAP_SESSION_HANDLE eap::peer_ttls::begin_session( } // We have configuration, we have credentials, create method. - s->m_method.reset(new method_ttls(*this, *cfg_method, *dynamic_cast(s->m_cred.m_cred.get()))); + auto cfg_inner = cfg_method->m_inner.get(); + auto cred_inner = dynamic_cast(s->m_cred.m_cred.get())->m_inner.get(); + auto cfg_inner_eaphost = dynamic_cast(cfg_inner); + unique_ptr meth_inner; + if (!cfg_inner_eaphost) { + // 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; + default: throw invalid_argument(__FUNCTION__ " Unsupported inner authentication method."); + } + } else { + // EapHost inner method + meth_inner.reset( + new method_eapmsg (*this, cred_inner->get_identity().c_str(), + new method_eaphost(*this, *cfg_inner_eaphost, dynamic_cast(*cred_inner)))); + } + s->m_method.reset( + new method_eap (*this, eap_type_ttls, + new method_defrag(*this, + new method_ttls (*this, *cfg_method, *dynamic_cast(s->m_cred.m_cred.get()), meth_inner.release())))); // Initialize method. s->m_method->begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); @@ -265,7 +285,14 @@ void eap::peer_ttls::get_response_packet( _Inout_bytecap_(*dwSendPacketSize) EapPacket *pSendPacket, _Inout_ DWORD *pdwSendPacketSize) { - static_cast(hSession)->m_method->get_response_packet(pSendPacket, pdwSendPacketSize); + assert(pdwSendPacketSize); + assert(pSendPacket || !*pdwSendPacketSize); + + sanitizing_blob packet; + static_cast(hSession)->m_method->get_response_packet(packet, *pdwSendPacketSize); + assert(packet.size() <= *pdwSendPacketSize); + + memcpy(pSendPacket, packet.data(), *pdwSendPacketSize = (DWORD)packet.size()); } @@ -277,9 +304,6 @@ void eap::peer_ttls::get_result( auto s = static_cast(hSession); s->m_method->get_result(reason, pResult); - s->m_eap_attr_desc.dwNumberOfAttributes = (DWORD)s->m_method->m_eap_attr.size(); - s->m_eap_attr_desc.pAttribs = s->m_method->m_eap_attr.data(); - pResult->pAttribArray = &s->m_eap_attr_desc; // Do not report failure to EapHost, as it will not save updated configuration then. But we need it to save it, to alert user on next connection attempt. // EapHost is well aware of the failed condition. diff --git a/lib/TTLS_UI/include/Module.h b/lib/TTLS_UI/include/Module.h index f676583..69689a5 100644 --- a/lib/TTLS_UI/include/Module.h +++ b/lib/TTLS_UI/include/Module.h @@ -39,7 +39,7 @@ namespace eap { public: /// - /// Constructs a EAP TTLS UI peer module + /// Constructs a EAP-TTLS UI peer module /// peer_ttls_ui(); diff --git a/lib/TTLS_UI/src/Module.cpp b/lib/TTLS_UI/src/Module.cpp index 2bc48b0..16c0acb 100644 --- a/lib/TTLS_UI/src/Module.cpp +++ b/lib/TTLS_UI/src/Module.cpp @@ -24,14 +24,20 @@ using namespace std; using namespace winstd; -////////////////////////////////////////////////////////////////////// -// wxInitializerPeer -////////////////////////////////////////////////////////////////////// - +/// +/// Peer initializer +/// class wxInitializerPeer { public: + /// + /// Initialize peer + /// wxInitializerPeer(_In_ HINSTANCE instance); + + /// + /// Uninitialize peer + /// virtual ~wxInitializerPeer(); protected: