Rename method_tls_tunnel to method_tls and move upstream

CRL checking was also moved upstream as method_tls triggers it.

Signed-off-by: Simon Rozman <simon@rozman.si>
This commit is contained in:
Simon Rozman 2020-02-04 14:00:28 +01:00
parent 5c0299197b
commit 1d558c939e
13 changed files with 1078 additions and 954 deletions

View File

@ -737,14 +737,14 @@ namespace eap
/// ///
/// \sa [EapPeerGetInfo function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363613.aspx) /// \sa [EapPeerGetInfo function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363613.aspx)
/// ///
virtual void initialize() = 0; virtual void initialize();
/// ///
/// Shuts down the EAP method and prepares to unload its corresponding DLL. /// Shuts down the EAP method and prepares to unload its corresponding DLL.
/// ///
/// \sa [EapPeerShutdown function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363627.aspx) /// \sa [EapPeerShutdown function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363627.aspx)
/// ///
virtual void shutdown() = 0; virtual void shutdown();
/// ///
/// Returns the user data and user identity after being called by EapHost. /// Returns the user data and user identity after being called by EapHost.

View File

@ -342,6 +342,16 @@ eap::peer::peer(_In_ eap_type_t eap_method) : module(eap_method)
} }
void eap::peer::initialize()
{
}
void eap::peer::shutdown()
{
}
void eap::peer::query_credential_input_fields( void eap::peer::query_credential_input_fields(
_In_ HANDLE hUserImpersonationToken, _In_ HANDLE hUserImpersonationToken,
_In_ DWORD dwFlags, _In_ DWORD dwFlags,

View File

@ -104,11 +104,13 @@
<ClInclude Include="..\include\Config.h" /> <ClInclude Include="..\include\Config.h" />
<ClInclude Include="..\include\Credentials.h" /> <ClInclude Include="..\include\Credentials.h" />
<ClInclude Include="..\include\Method.h" /> <ClInclude Include="..\include\Method.h" />
<ClInclude Include="..\include\Module.h" />
<ClInclude Include="..\src\StdAfx.h" /> <ClInclude Include="..\src\StdAfx.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\Config.cpp" /> <ClCompile Include="..\src\Config.cpp" />
<ClCompile Include="..\src\Method.cpp" /> <ClCompile Include="..\src\Method.cpp" />
<ClCompile Include="..\src\Module.cpp" />
<ClCompile Include="..\src\StdAfx.cpp"> <ClCompile Include="..\src\StdAfx.cpp">
<PrecompiledHeader>Create</PrecompiledHeader> <PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile> </ClCompile>

View File

@ -23,6 +23,9 @@
<ClInclude Include="..\include\Method.h"> <ClInclude Include="..\include\Method.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\include\Module.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\StdAfx.cpp"> <ClCompile Include="..\src\StdAfx.cpp">
@ -37,5 +40,8 @@
<ClCompile Include="..\src\Method.cpp"> <ClCompile Include="..\src\Method.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\src\Module.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -21,12 +21,18 @@
namespace eap namespace eap
{ {
class method_defrag; class method_defrag;
class method_tls;
} }
#pragma once #pragma once
#include "Config.h"
#include "Credentials.h"
#include "../../EAPBase/include/Method.h" #include "../../EAPBase/include/Method.h"
#include <WinStd/Sec.h>
namespace eap namespace eap
{ {
@ -111,5 +117,90 @@ namespace eap
} m_phase; ///< What phase is our communication at? } m_phase; ///< What phase is our communication at?
}; };
///
/// EAP-TLS method
///
class method_tls : public method
{
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
/// \param[in] inner Inner method
///
method_tls(_In_ module &mod, _In_ config_method_tls &cfg, _In_ credentials_tls &cred, _In_opt_ method *inner = nullptr);
/// \name Session management
/// @{
virtual void begin_session(
_In_ DWORD dwFlags,
_In_ const EapAttributes *pAttributeArray,
_In_ HANDLE hTokenImpersonateUser,
_In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD);
/// @}
/// \name Packet processing
/// @{
virtual EapPeerMethodResponseAction process_request_packet(
_In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket,
_In_ DWORD dwReceivedPacketSize);
virtual void get_response_packet(
_Out_ sanitizing_blob &packet,
_In_opt_ DWORD size_max = MAXDWORD);
/// @}
virtual void get_result(
_In_ EapPeerMethodResultReason reason,
_Inout_ EapPeerMethodResult *pResult);
protected:
///
/// Decrypts data and forwards it to the inner method.
///
EapPeerMethodResponseAction decrypt_request_data();
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
///
/// Verifies server certificate if trusted by configuration
///
void verify_server_trust() const;
#endif
protected:
config_method_tls &m_cfg; ///< Method configuration
credentials_tls &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<unsigned char> m_sc_queue; ///< TLS data queue
winstd::sec_context m_sc_ctx; ///< Schannel context
winstd::cert_context m_sc_cert; ///< Server certificate
///
/// Communication phase
///
enum class phase_t {
unknown = -1, ///< Unknown phase
handshake_init = 0, ///< Handshake initialize
handshake_cont, ///< Handshake continue
finished, ///< Exchange application data
} m_phase; ///< What phase is our communication at?
sanitizing_blob m_packet_res; ///< Response packet
bool m_packet_res_inner; ///< Get and encrypt data from inner method too?
std::vector<winstd::eap_attr> 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())
};
/// @} /// @}
} }

110
lib/TLS/include/Module.h Normal file
View File

@ -0,0 +1,110 @@
/*
Copyright 2015-2020 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 <http://www.gnu.org/licenses/>.
*/
namespace eap
{
class peer_tls;
}
#pragma once
#include "Config.h"
#include "Credentials.h"
#include "Method.h"
namespace eap
{
/// \addtogroup EAPBaseModule
/// @{
///
/// TLS tunnel peer
///
class peer_tls : public peer
{
public:
///
/// Constructs a TLS tunnel peer module
///
/// \param[in] eap_method EAP method type ID
///
peer_tls(_In_ winstd::eap_type_t eap_method = winstd::eap_type_t::tls);
virtual void shutdown();
///
/// Spawns a new certificate revocation check thread
///
/// \param[inout] cert Certificate context to check for revocation. `hCertStore` member should contain all certificates in chain up to and including root CA to test them for revocation too.
///
void spawn_crl_check(_Inout_ winstd::cert_context &&cert);
protected:
///
///< Post-festum server certificate revocation verify thread
///
class crl_checker {
public:
///
/// Constructs a thread
///
/// \param[in ] mod EAP module to use for global services
/// \param[inout] cert Certificate context to check for revocation. `hCertStore` member should contain all certificates in chain up to and including root CA to test them for revocation too.
///
crl_checker(_In_ module &mod, _Inout_ winstd::cert_context &&cert);
///
/// Moves a thread
///
/// \param[in] other Thread to move from
///
crl_checker(_Inout_ crl_checker &&other) noexcept;
///
/// Moves a thread
///
/// \param[in] other Thread to move from
///
/// \returns Reference to this object
///
crl_checker& operator=(_Inout_ crl_checker &&other) noexcept;
///
/// Verifies server's certificate if it has been revoked
///
/// \param[in] obj Pointer to the instance of this object
///
/// \returns Thread exit code
///
static DWORD WINAPI verify(_In_ crl_checker *obj);
public:
module &m_module; ///< Module
winstd::win_handle<NULL> m_thread; ///< Thread
winstd::win_handle<NULL> m_abort; ///< Thread abort event
winstd::cert_context m_cert; ///< Server certificate
};
std::list<crl_checker> m_crl_checkers; ///< List of certificate revocation check threads
};
/// @}
}

View File

@ -20,6 +20,8 @@
#include "StdAfx.h" #include "StdAfx.h"
#pragma comment(lib, "Secur32.lib")
using namespace std; using namespace std;
using namespace winstd; using namespace winstd;
@ -154,3 +156,645 @@ void eap::method_defrag::get_response_packet(
m_send_res = false; m_send_res = false;
} }
} }
//////////////////////////////////////////////////////////////////////
// eap::method_tls
//////////////////////////////////////////////////////////////////////
eap::method_tls::method_tls(_In_ module &mod, _In_ config_method_tls &cfg, _In_ credentials_tls &cred, _In_opt_ method *inner) :
m_cfg(cfg),
m_cred(cred),
m_user_ctx(NULL),
m_phase(phase_t::unknown),
m_packet_res_inner(false),
method(mod, inner)
{
m_eap_attr_desc.dwNumberOfAttributes = 0;
m_eap_attr_desc.pAttribs = NULL;
}
void eap::method_tls::begin_session(
_In_ DWORD dwFlags,
_In_ const EapAttributes *pAttributeArray,
_In_ HANDLE hTokenImpersonateUser,
_In_opt_ DWORD dwMaxSendPacketSize)
{
// In TLS, maximum packet length can precisely be calculated only after handshake is complete.
// Therefore, we allow inner method same maximum packet size as this method.
// Initialize tunnel and inner method session with same parameters.
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_t::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, 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 ? 1ul : 0ul, // 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.
0x00400000ul /*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.");
m_phase = phase_t::handshake_init;
}
EapPeerMethodResponseAction eap::method_tls::process_request_packet(
_In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket,
_In_ DWORD dwReceivedPacketSize)
{
assert(pReceivedPacket || dwReceivedPacketSize == 0);
user_impersonator impersonating(m_user_ctx);
switch (m_phase) {
case phase_t::handshake_init: {
m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank);
// Prepare input buffer(s).
SecBuffer buf_in[] = {
{ (unsigned long)dwReceivedPacketSize, SECBUFFER_TOKEN, const_cast<void*>(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<const unsigned char*>(buf_out[0].pvBuffer), reinterpret_cast<const unsigned char*>(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<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize - buf_in[1].cbBuffer,
reinterpret_cast<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize);
} else
m_sc_queue.clear();
m_phase = phase_t::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<const unsigned char*>(buf_out[1].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::handshake_cont: {
m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast<const unsigned char*>(pReceivedPacket), reinterpret_cast<const unsigned char*>(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 (status == SEC_E_OK) {
// Get server certificate.
if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&m_sc_cert)))
throw sec_runtime_error(status, __FUNCTION__ " Error retrieving server certificate from Schannel.");
// Add all trusted root CAs to server certificate's store. This allows CertGetIssuerCertificateFromStore() in the following CRL check to test the root CA for revocation too.
// verify_server_trust(), ignores all self-signed certificates from the server certificate's store, and rebuilds its own trusted root store, so we are safe to do this.
for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c)
CertAddCertificateContextToStore(m_sc_cert->hCertStore, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL);
// Verify cached CRL (entire chain).
reg_key key;
if (key.open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\") _T(VENDOR_NAME_STR) _T("\\") _T(PRODUCT_NAME_STR) _T("\\TLSCRL"), 0, KEY_READ)) {
wstring hash_unicode;
vector<unsigned char> hash, subj;
for (cert_context c(m_sc_cert); c;) {
if (CertGetCertificateContextProperty(c, CERT_HASH_PROP_ID, hash)) {
hash_unicode.clear();
hex_enc enc;
enc.encode(hash_unicode, hash.data(), hash.size());
if (RegQueryValueExW(key, hash_unicode.c_str(), NULL, NULL, subj) == ERROR_SUCCESS) {
// A certificate in the chain is found to be revoked as compromised.
m_cfg.m_last_status = config_method::status_t::server_compromised;
throw com_runtime_error(CRYPT_E_REVOKED, __FUNCTION__ " Server certificate or one of its issuer's certificate has been found revoked as compromised. Your credentials were probably sent to this server during previous connection attempts, thus changing your credentials (in a safe manner) is strongly advised. Please, contact your helpdesk immediately.");
}
}
DWORD flags = 0;
c = CertGetIssuerCertificateFromStore(m_sc_cert->hCertStore, c, NULL, &flags);
if (!c) break;
}
}
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
// Verify server certificate chain.
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<const unsigned char*>(buf_out[0].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::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)m_cfg.get_method_id()),
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_t::finished;
m_cfg.m_last_status = config_method::status_t::auth_failed; // Blame protocol if we fail beyond this point.
method_mschapv2_diameter *inner_mschapv2 = dynamic_cast<method_mschapv2_diameter*>(m_inner.get());
if (inner_mschapv2) {
// Push EAP-TTLS keying material to inner MSCHAPv2 method.
static const DWORD key_id = 0x02; // EAP-TTLSv0 Challenge Data
static const SecPkgContext_EapPrfInfo prf_info = { 0, sizeof(key_id), (PBYTE)&key_id };
if (FAILED(status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&prf_info, sizeof(prf_info))))
throw sec_runtime_error(status, __FUNCTION__ " Error setting TTLS PRF in Schannel.");
SecPkgContext_EapKeyBlock key_block;
if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block)))
throw sec_runtime_error(status, __FUNCTION__ " Error generating PRF in Schannel.");
inner_mschapv2->set_challenge_data(key_block.rgbKeys, key_block.rgbKeys[sizeof(challenge_mschapv2)]);
SecureZeroMemory(&key_block, sizeof(key_block));
}
// Piggyback initial inner response.
decrypt_request_data();
}
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<const unsigned char*>(buf_out[1].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::finished: {
m_packet_res.clear();
m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast<const unsigned char*>(pReceivedPacket), reinterpret_cast<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize);
return decrypt_request_data();
}
default:
throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase));
}
}
void eap::method_tls::get_response_packet(
_Out_ sanitizing_blob &packet,
_In_opt_ DWORD size_max)
{
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: %zu, maximum: %u).", m_packet_res.size(), size_max));
sizes.cbMaximumMessage = std::min<unsigned long>(sizes.cbMaximumMessage, size_max - (unsigned long)(m_packet_res.size() + sizes.cbHeader + sizes.cbTrailer));
// Get inner response packet.
packet.reserve((size_t)sizes.cbHeader + sizes.cbMaximumMessage + sizes.cbTrailer);
method::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<const unsigned char*>(buf[0].pvBuffer),
reinterpret_cast<const unsigned char*>(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: %zu, maximum: %u).", m_packet_res.size(), size_max));
packet.assign(m_packet_res.begin(), m_packet_res.end());
}
void eap::method_tls::get_result(
_In_ EapPeerMethodResultReason reason,
_Inout_ EapPeerMethodResult *pResult)
{
assert(pResult);
// Get inner result.
method::get_result(reason, pResult);
if (reason == EapPeerMethodResultSuccess) {
eap_attr a;
// Prepare EAP result attributes.
if (pResult->pAttribArray) {
m_eap_attr.reserve((size_t)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<const unsigned int*>(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();
}
// Derive MSK keys.
DWORD key_id =
m_cfg.get_method_id() == eap_type_t::ttls ? 0x01 : // EAP-TTLSv0 Keying Material
0x00; // PPP EAP TLS Key Data
const SecPkgContext_EapPrfInfo prf_info = { 0, sizeof(key_id), (PBYTE)&key_id };
SECURITY_STATUS status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&prf_info, sizeof(prf_info));
if (FAILED(status))
throw sec_runtime_error(status, __FUNCTION__ " Error setting 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.");
const unsigned char *_key_block = key_block.rgbKeys;
// MSK: MPPE-Recv-Key
a.create_ms_mppe_key(16, _key_block, 32);
m_eap_attr.push_back(std::move(a));
_key_block += 32;
// MSK: MPPE-Send-Key
a.create_ms_mppe_key(17, _key_block, 32);
m_eap_attr.push_back(std::move(a));
_key_block += 32;
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_t::success;
// Spawn certificate revocation verify thread.
dynamic_cast<peer_tls&>(m_module).spawn_crl_check(std::move(m_sc_cert));
}
// Ask EAP host to save the configuration (connection data).
pResult->fSaveConnectionData = TRUE;
}
EapPeerMethodResponseAction eap::method_tls::decrypt_request_data()
{
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 data for inner authentication avaliable.
action = method::process_request_packet(NULL, 0);
} else {
// Authenticator sent data for inner authentication. Decrypt 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::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<const unsigned char*>(buf[i].pvBuffer), reinterpret_cast<const unsigned char*>(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.");
}
m_packet_res_inner = action == EapPeerMethodResponseActionSend;
return action;
}
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
void eap::method_tls::verify_server_trust() const
{
for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) {
if (m_sc_cert->cbCertEncoded == (*c)->cbCertEncoded &&
memcmp(m_sc_cert->pbCertEncoded, (*c)->pbCertEncoded, m_sc_cert->cbCertEncoded) == 0)
{
// Server certificate found directly on the trusted root CA list.
m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED_EX1, event_data((unsigned int)m_cfg.get_method_id()), 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 < m_sc_cert->pCertInfo->cExtension; idx_ext++) {
unique_ptr<CERT_ALT_NAME_INFO, LocalFree_delete<CERT_ALT_NAME_INFO> > san_info;
if (strcmp(m_sc_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,
m_sc_cert->pCertInfo->rgExtension[idx_ext].Value.pbData, m_sc_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(m_sc_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,
m_sc_cert->pCertInfo->rgExtension[idx_ext].Value.pbData, m_sc_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_TRUSTED2, event_data((unsigned int)m_cfg.get_method_id()), 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(m_sc_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_TRUSTED2, event_data((unsigned int)m_cfg.get_method_id()), 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 (m_sc_cert->pCertInfo->Issuer.cbData == m_sc_cert->pCertInfo->Subject.cbData &&
memcmp(m_sc_cert->pCertInfo->Issuer.pbData, m_sc_cert->pCertInfo->Subject.pbData, m_sc_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(m_sc_cert); c;) {
DWORD flags = 0;
c.attach(CertGetIssuerCertificateFromStore(m_sc_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);
}
// 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, m_sc_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_TRUSTED1, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank);
}
#endif

199
lib/TLS/src/Module.cpp Normal file
View File

@ -0,0 +1,199 @@
/*
Copyright 2015-2020 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 <http://www.gnu.org/licenses/>.
*/
#include "StdAfx.h"
using namespace std;
using namespace winstd;
//////////////////////////////////////////////////////////////////////
// eap::peer_tls
//////////////////////////////////////////////////////////////////////
eap::peer_tls::peer_tls(_In_ eap_type_t eap_method) : peer(eap_method)
{
}
void eap::peer_tls::shutdown()
{
// Signal all certificate revocation verify threads to abort and wait for them (10sec max).
vector<HANDLE> chks;
chks.reserve(m_crl_checkers.size());
for (auto chk = m_crl_checkers.begin(), chk_end = m_crl_checkers.end(); chk != chk_end; ++chk) {
SetEvent(chk->m_abort);
chks.push_back(chk->m_thread);
}
WaitForMultipleObjects((DWORD)chks.size(), chks.data(), TRUE, 10000);
peer::shutdown();
}
void eap::peer_tls::spawn_crl_check(_Inout_ winstd::cert_context &&cert)
{
// Create the thread and add it to the list.
m_crl_checkers.push_back(std::move(crl_checker(*this, std::move(cert))));
// Now the thread is in-place, start it.
crl_checker &chk = m_crl_checkers.back();
chk.m_thread = CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(crl_checker::verify), &chk, 0, NULL);
}
//////////////////////////////////////////////////////////////////////
// eap::peer_tls::crl_checker
//////////////////////////////////////////////////////////////////////
eap::peer_tls::crl_checker::crl_checker(_In_ module &mod, _Inout_ winstd::cert_context &&cert) :
m_module(mod),
m_cert (std::move(cert)),
m_abort (CreateEvent(NULL, TRUE, FALSE, NULL))
{
}
eap::peer_tls::crl_checker::crl_checker(_Inout_ crl_checker &&other) noexcept :
m_module( other.m_module ),
m_thread(std::move(other.m_thread)),
m_abort (std::move(other.m_abort )),
m_cert (std::move(other.m_cert ))
{
}
eap::peer_tls::crl_checker& eap::peer_tls::crl_checker::operator=(_Inout_ crl_checker &&other) noexcept
{
if (this != std::addressof(other)) {
assert(std::addressof(m_module) == std::addressof(other.m_module)); // Move threads within same module only!
m_thread = std::move(other.m_thread);
m_abort = std::move(other.m_abort );
m_cert = std::move(other.m_cert );
}
return *this;
}
DWORD WINAPI eap::peer_tls::crl_checker::verify(_In_ crl_checker *obj)
{
// Initialize COM.
com_initializer com_init(NULL);
// Wait for 5sec for the link to become online. (Hopefuly!)
if (WaitForSingleObject(obj->m_abort, 5000) == WAIT_OBJECT_0) {
// Aborted.
return 1;
}
// Prepare a list of certificates forming certificate chain.
list<cert_context> context_data;
for (cert_context c(obj->m_cert); c;) {
context_data.push_back(std::move(c));
DWORD flags = 0;
c = CertGetIssuerCertificateFromStore(obj->m_cert->hCertStore, context_data.back(), NULL, &flags);
if (!c) break;
}
// Create an array of pointers to CERT_CONTEXT required by CertVerifyRevocation().
vector<PCERT_CONTEXT> context;
context.reserve(context_data.size());
for (auto c = context_data.cbegin(), c_end = context_data.cend(); c != c_end; ++c)
context.push_back(const_cast<PCERT_CONTEXT>(c->operator PCCERT_CONTEXT()));
CERT_REVOCATION_STATUS status_rev = { sizeof(CERT_REVOCATION_STATUS) };
for (auto c = context.begin(), c_end = context.end(); c != c_end;) {
// Check for thread abort signal.
if (WaitForSingleObject(obj->m_abort, 0) == WAIT_OBJECT_0)
return 1;
// Perform revocation check.
if (!CertVerifyRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE,
(DWORD)(c_end - c), reinterpret_cast<PVOID*>(&*c),
CERT_VERIFY_REV_CHAIN_FLAG, NULL, &status_rev))
{
PCCERT_CONTEXT cert = *(c + status_rev.dwIndex);
wstring subj;
if (!CertGetNameStringW(cert, CERT_NAME_DNS_TYPE, CERT_NAME_STR_ENABLE_PUNYCODE_FLAG, NULL, subj))
sprintf(subj, L"(error %u)", GetLastError());
switch (status_rev.dwError) {
case CRYPT_E_NO_REVOCATION_CHECK:
// Revocation check could not be performed.
c += (size_t)status_rev.dwIndex + 1;
if (c == c_end) {
// This "error" is expected for the root CA certificate.
} else {
// This really was an error, as it appeared before the root CA cerficate in the chain.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_SKIPPED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data::blank);
}
break;
case CRYPT_E_REVOKED:
// One of the certificates in the chain was revoked.
switch (status_rev.dwReason) {
case CRL_REASON_AFFILIATION_CHANGED:
case CRL_REASON_SUPERSEDED:
case CRL_REASON_CESSATION_OF_OPERATION:
case CRL_REASON_CERTIFICATE_HOLD:
// The revocation was of administrative nature. No need to black-list.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED1, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
break;
default: {
// One of the certificates in the chain was revoked as compromised. Black-list it.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
reg_key key;
if (key.create(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\") _T(VENDOR_NAME_STR) _T("\\") _T(PRODUCT_NAME_STR) _T("\\TLSCRL"), NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE)) {
vector<unsigned char> hash;
if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, hash)) {
wstring hash_unicode;
hex_enc enc;
enc.encode(hash_unicode, hash.data(), hash.size());
RegSetValueExW(key, hash_unicode.c_str(), NULL, REG_SZ, reinterpret_cast<LPCBYTE>(subj.c_str()), (DWORD)((subj.length() + 1) * sizeof(wstring::value_type)));
}
}
}}
// Resume checking the rest of the chain.
c += (size_t)status_rev.dwIndex + 1;
break;
case ERROR_SUCCESS:
// Odd. CertVerifyRevocation() should return TRUE then. Nevertheless, we take this as a "yes".
c = c_end;
break;
default:
// Checking one of the certificates in the chain for revocation failed. Resume checking the rest.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FAILED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwError), event_data::blank);
c += (size_t)status_rev.dwIndex + 1;
}
} else {
// Revocation check finished.
break;
}
}
// Revocation check succeeded.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FINISHED, event_data((unsigned int)obj->m_module.m_eap_method), event_data::blank);
return 0;
}

View File

@ -23,6 +23,9 @@
#include "../include/Config.h" #include "../include/Config.h"
#include "../include/Credentials.h" #include "../include/Credentials.h"
#include "../include/Method.h" #include "../include/Method.h"
#include "../include/Module.h"
#include "../../MSCHAPv2/include/Method.h"
#include "../../EAPBase/include/EAPXML.h" #include "../../EAPBase/include/EAPXML.h"

View File

@ -21,7 +21,6 @@
namespace eap namespace eap
{ {
class method_eapmsg; class method_eapmsg;
class method_tls_tunnel;
} }
#pragma once #pragma once
@ -32,8 +31,6 @@ namespace eap
#include "../../EAPBase/include/Method.h" #include "../../EAPBase/include/Method.h"
#include <WinStd/Sec.h>
namespace eap namespace eap
{ {
@ -91,92 +88,5 @@ namespace eap
sanitizing_blob m_packet_res; ///< Response packet sanitizing_blob m_packet_res; ///< Response packet
}; };
///
/// TLS tunnel method
///
class method_tls_tunnel : public method
{
public:
///
/// Constructs a TLS tunnel method
///
/// \param[in] mod EAP module to use for global services
/// \param[in] eap_method EAP method type ID
/// \param[in] cfg Method configuration
/// \param[in] cred User credentials
/// \param[in] inner Inner method
///
method_tls_tunnel(_In_ module &mod, _In_ winstd::eap_type_t eap_method, _In_ config_method_tls_tunnel &cfg, _In_ credentials_tls_tunnel &cred, _In_ method *inner);
/// \name Session management
/// @{
virtual void begin_session(
_In_ DWORD dwFlags,
_In_ const EapAttributes *pAttributeArray,
_In_ HANDLE hTokenImpersonateUser,
_In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD);
/// @}
/// \name Packet processing
/// @{
virtual EapPeerMethodResponseAction process_request_packet(
_In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket,
_In_ DWORD dwReceivedPacketSize);
virtual void get_response_packet(
_Out_ sanitizing_blob &packet,
_In_opt_ DWORD size_max = MAXDWORD);
/// @}
virtual void get_result(
_In_ EapPeerMethodResultReason reason,
_Inout_ EapPeerMethodResult *pResult);
protected:
///
/// Decrypts data and forwards it to the inner method.
///
EapPeerMethodResponseAction decrypt_request_data();
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
///
/// Verifies server certificate if trusted by configuration
///
void verify_server_trust() const;
#endif
protected:
const winstd::eap_type_t m_eap_method; ///< EAP method type
config_method_tls_tunnel &m_cfg; ///< Method configuration
credentials_tls_tunnel &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<unsigned char> m_sc_queue; ///< TLS data queue
winstd::sec_context m_sc_ctx; ///< Schannel context
winstd::cert_context m_sc_cert; ///< Server certificate
///
/// Communication phase
///
enum class phase_t {
unknown = -1, ///< Unknown phase
handshake_init = 0, ///< Handshake initialize
handshake_cont, ///< Handshake continue
finished, ///< Exchange application data
} m_phase; ///< What phase is our communication at?
sanitizing_blob m_packet_res; ///< Response packet
bool m_packet_res_inner; ///< Get and ancrypt data from inner method too?
std::vector<winstd::eap_attr> 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())
};
/// @} /// @}
} }

View File

@ -31,6 +31,8 @@ namespace eap
#include "Method.h" #include "Method.h"
#include "TTLS.h" #include "TTLS.h"
#include "..\..\TLS\include\Module.h"
namespace eap namespace eap
{ {
@ -40,7 +42,7 @@ namespace eap
/// ///
/// TLS tunnel peer /// TLS tunnel peer
/// ///
class peer_tls_tunnel : public peer class peer_tls_tunnel : public peer_tls
{ {
public: public:
/// ///
@ -151,13 +153,6 @@ namespace eap
/// @} /// @}
///
/// Spawns a new certificate revocation check thread
///
/// \param[inout] cert Certificate context to check for revocation. `hCertStore` member should contain all certificates in chain up to and including root CA to test them for revocation too.
///
void spawn_crl_check(_Inout_ winstd::cert_context &&cert);
protected: protected:
/// ///
/// Makes a new inner method /// Makes a new inner method
@ -209,53 +204,6 @@ namespace eap
#endif #endif
BYTE *m_blob_ui_ctx; ///< User Interface context data BYTE *m_blob_ui_ctx; ///< User Interface context data
}; };
///
///< Post-festum server certificate revocation verify thread
///
class crl_checker {
public:
///
/// Constructs a thread
///
/// \param[in ] mod EAP module to use for global services
/// \param[inout] cert Certificate context to check for revocation. `hCertStore` member should contain all certificates in chain up to and including root CA to test them for revocation too.
///
crl_checker(_In_ module &mod, _Inout_ winstd::cert_context &&cert);
///
/// Moves a thread
///
/// \param[in] other Thread to move from
///
crl_checker(_Inout_ crl_checker &&other) noexcept;
///
/// Moves a thread
///
/// \param[in] other Thread to move from
///
/// \returns Reference to this object
///
crl_checker& operator=(_Inout_ crl_checker &&other) noexcept;
///
/// Verifies server's certificate if it has been revoked
///
/// \param[in] obj Pointer to the instance of this object
///
/// \returns Thread exit code
///
static DWORD WINAPI verify(_In_ crl_checker *obj);
public:
module &m_module; ///< Module
winstd::win_handle<NULL> m_thread; ///< Thread
winstd::win_handle<NULL> m_abort; ///< Thread abort event
winstd::cert_context m_cert; ///< Server certificate
};
std::list<crl_checker> m_crl_checkers; ///< List of certificate revocation check threads
}; };

View File

@ -20,8 +20,6 @@
#include "StdAfx.h" #include "StdAfx.h"
#pragma comment(lib, "Secur32.lib")
using namespace std; using namespace std;
using namespace winstd; using namespace winstd;
@ -151,644 +149,3 @@ void eap::method_eapmsg::get_response_packet(
packet.assign(m_packet_res.cbegin(), m_packet_res.cend()); packet.assign(m_packet_res.cbegin(), m_packet_res.cend());
} }
} }
//////////////////////////////////////////////////////////////////////
// eap::method_tls_tunnel
//////////////////////////////////////////////////////////////////////
eap::method_tls_tunnel::method_tls_tunnel(_In_ module &mod, _In_ eap_type_t eap_method, _In_ config_method_tls_tunnel &cfg, _In_ credentials_tls_tunnel &cred, _In_ method *inner) :
m_eap_method(eap_method),
m_cfg(cfg),
m_cred(cred),
m_user_ctx(NULL),
m_phase(phase_t::unknown),
m_packet_res_inner(false),
method(mod, inner)
{
m_eap_attr_desc.dwNumberOfAttributes = 0;
m_eap_attr_desc.pAttribs = NULL;
}
void eap::method_tls_tunnel::begin_session(
_In_ DWORD dwFlags,
_In_ const EapAttributes *pAttributeArray,
_In_ HANDLE hTokenImpersonateUser,
_In_opt_ DWORD dwMaxSendPacketSize)
{
// In TLS, maximum packet length can precisely be calculated only after handshake is complete.
// Therefore, we allow inner method same maximum packet size as this method.
// Initialize tunnel and inner method session with same parameters.
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_t::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, 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 ? 1ul : 0ul, // 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.
0x00400000ul /*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.");
m_phase = phase_t::handshake_init;
}
EapPeerMethodResponseAction eap::method_tls_tunnel::process_request_packet(
_In_bytecount_(dwReceivedPacketSize) const void *pReceivedPacket,
_In_ DWORD dwReceivedPacketSize)
{
assert(pReceivedPacket || dwReceivedPacketSize == 0);
user_impersonator impersonating(m_user_ctx);
switch (m_phase) {
case phase_t::handshake_init: {
m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)m_eap_method), event_data::blank);
// Prepare input buffer(s).
SecBuffer buf_in[] = {
{ (unsigned long)dwReceivedPacketSize, SECBUFFER_TOKEN, const_cast<void*>(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<const unsigned char*>(buf_out[0].pvBuffer), reinterpret_cast<const unsigned char*>(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<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize - buf_in[1].cbBuffer,
reinterpret_cast<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize);
} else
m_sc_queue.clear();
m_phase = phase_t::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<const unsigned char*>(buf_out[1].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::handshake_cont: {
m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast<const unsigned char*>(pReceivedPacket), reinterpret_cast<const unsigned char*>(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 (status == SEC_E_OK) {
// Get server certificate.
if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&m_sc_cert)))
throw sec_runtime_error(status, __FUNCTION__ " Error retrieving server certificate from Schannel.");
// Add all trusted root CAs to server certificate's store. This allows CertGetIssuerCertificateFromStore() in the following CRL check to test the root CA for revocation too.
// verify_server_trust(), ignores all self-signed certificates from the server certificate's store, and rebuilds its own trusted root store, so we are safe to do this.
for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c)
CertAddCertificateContextToStore(m_sc_cert->hCertStore, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL);
// Verify cached CRL (entire chain).
reg_key key;
if (key.open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\") _T(VENDOR_NAME_STR) _T("\\") _T(PRODUCT_NAME_STR) _T("\\TLSCRL"), 0, KEY_READ)) {
wstring hash_unicode;
vector<unsigned char> hash, subj;
for (cert_context c(m_sc_cert); c;) {
if (CertGetCertificateContextProperty(c, CERT_HASH_PROP_ID, hash)) {
hash_unicode.clear();
hex_enc enc;
enc.encode(hash_unicode, hash.data(), hash.size());
if (RegQueryValueExW(key, hash_unicode.c_str(), NULL, NULL, subj) == ERROR_SUCCESS) {
// A certificate in the chain is found to be revoked as compromised.
m_cfg.m_last_status = config_method::status_t::server_compromised;
throw com_runtime_error(CRYPT_E_REVOKED, __FUNCTION__ " Server certificate or one of its issuer's certificate has been found revoked as compromised. Your credentials were probably sent to this server during previous connection attempts, thus changing your credentials (in a safe manner) is strongly advised. Please, contact your helpdesk immediately.");
}
}
DWORD flags = 0;
c = CertGetIssuerCertificateFromStore(m_sc_cert->hCertStore, c, NULL, &flags);
if (!c) break;
}
}
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
// Verify server certificate chain.
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<const unsigned char*>(buf_out[0].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::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)m_eap_method),
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_t::finished;
m_cfg.m_last_status = config_method::status_t::auth_failed; // Blame protocol if we fail beyond this point.
method_mschapv2_diameter *inner_mschapv2 = dynamic_cast<method_mschapv2_diameter*>(m_inner.get());
if (inner_mschapv2) {
// Push EAP-TTLS keying material to inner MSCHAPv2 method.
static const DWORD key_id = 0x02; // EAP-TTLSv0 Challenge Data
static const SecPkgContext_EapPrfInfo prf_info = { 0, sizeof(key_id), (PBYTE)&key_id };
if (FAILED(status = SetContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_PRF_INFO, (void*)&prf_info, sizeof(prf_info))))
throw sec_runtime_error(status, __FUNCTION__ " Error setting TTLS PRF in Schannel.");
SecPkgContext_EapKeyBlock key_block;
if (FAILED(status = QueryContextAttributes(m_sc_ctx, SECPKG_ATTR_EAP_KEY_BLOCK, &key_block)))
throw sec_runtime_error(status, __FUNCTION__ " Error generating PRF in Schannel.");
inner_mschapv2->set_challenge_data(key_block.rgbKeys, key_block.rgbKeys[sizeof(challenge_mschapv2)]);
SecureZeroMemory(&key_block, sizeof(key_block));
}
// Piggyback initial inner response.
decrypt_request_data();
}
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<const unsigned char*>(buf_out[1].pvBuffer), reinterpret_cast<const unsigned char*>(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_t::finished: {
m_packet_res.clear();
m_sc_queue.insert(m_sc_queue.end(), reinterpret_cast<const unsigned char*>(pReceivedPacket), reinterpret_cast<const unsigned char*>(pReceivedPacket) + dwReceivedPacketSize);
return decrypt_request_data();
}
default:
throw invalid_argument(string_printf(__FUNCTION__ " Unknown phase (phase %u).", m_phase));
}
}
void eap::method_tls_tunnel::get_response_packet(
_Out_ sanitizing_blob &packet,
_In_opt_ DWORD size_max)
{
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: %zu, maximum: %u).", m_packet_res.size(), size_max));
sizes.cbMaximumMessage = std::min<unsigned long>(sizes.cbMaximumMessage, size_max - (unsigned long)(m_packet_res.size() + sizes.cbHeader + sizes.cbTrailer));
// Get inner response packet.
packet.reserve((size_t)sizes.cbHeader + sizes.cbMaximumMessage + sizes.cbTrailer);
method::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<const unsigned char*>(buf[0].pvBuffer),
reinterpret_cast<const unsigned char*>(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: %zu, maximum: %u).", m_packet_res.size(), size_max));
packet.assign(m_packet_res.begin(), m_packet_res.end());
}
void eap::method_tls_tunnel::get_result(
_In_ EapPeerMethodResultReason reason,
_Inout_ EapPeerMethodResult *pResult)
{
assert(pResult);
// Get inner result.
method::get_result(reason, pResult);
if (reason == EapPeerMethodResultSuccess) {
eap_attr a;
// Prepare EAP result attributes.
if (pResult->pAttribArray) {
m_eap_attr.reserve((size_t)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<const unsigned int*>(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();
}
// 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 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.");
const unsigned char *_key_block = key_block.rgbKeys;
// MSK: MPPE-Recv-Key
a.create_ms_mppe_key(16, _key_block, 32);
m_eap_attr.push_back(std::move(a));
_key_block += 32;
// MSK: MPPE-Send-Key
a.create_ms_mppe_key(17, _key_block, 32);
m_eap_attr.push_back(std::move(a));
_key_block += 32;
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_t::success;
// Spawn certificate revocation verify thread.
dynamic_cast<peer_tls_tunnel&>(m_module).spawn_crl_check(std::move(m_sc_cert));
}
// Ask EAP host to save the configuration (connection data).
pResult->fSaveConnectionData = TRUE;
}
EapPeerMethodResponseAction eap::method_tls_tunnel::decrypt_request_data()
{
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 data for inner authentication avaliable.
action = method::process_request_packet(NULL, 0);
} else {
// Authenticator sent data for inner authentication. Decrypt 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::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<const unsigned char*>(buf[i].pvBuffer), reinterpret_cast<const unsigned char*>(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.");
}
m_packet_res_inner = action == EapPeerMethodResponseActionSend;
return action;
}
#if EAP_TLS < EAP_TLS_SCHANNEL_FULL
void eap::method_tls_tunnel::verify_server_trust() const
{
for (auto c = m_cfg.m_trusted_root_ca.cbegin(), c_end = m_cfg.m_trusted_root_ca.cend(); c != c_end; ++c) {
if (m_sc_cert->cbCertEncoded == (*c)->cbCertEncoded &&
memcmp(m_sc_cert->pbCertEncoded, (*c)->pbCertEncoded, m_sc_cert->cbCertEncoded) == 0)
{
// Server certificate found directly on the trusted root CA list.
m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_TRUSTED_EX1, event_data((unsigned int)m_eap_method), 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 < m_sc_cert->pCertInfo->cExtension; idx_ext++) {
unique_ptr<CERT_ALT_NAME_INFO, LocalFree_delete<CERT_ALT_NAME_INFO> > san_info;
if (strcmp(m_sc_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,
m_sc_cert->pCertInfo->rgExtension[idx_ext].Value.pbData, m_sc_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(m_sc_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,
m_sc_cert->pCertInfo->rgExtension[idx_ext].Value.pbData, m_sc_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_TRUSTED2, event_data((unsigned int)m_eap_method), 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(m_sc_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_TRUSTED2, event_data((unsigned int)m_eap_method), 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 (m_sc_cert->pCertInfo->Issuer.cbData == m_sc_cert->pCertInfo->Subject.cbData &&
memcmp(m_sc_cert->pCertInfo->Issuer.pbData, m_sc_cert->pCertInfo->Subject.pbData, m_sc_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(m_sc_cert); c;) {
DWORD flags = 0;
c.attach(CertGetIssuerCertificateFromStore(m_sc_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);
}
// 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, m_sc_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_TRUSTED1, event_data((unsigned int)m_eap_method), event_data::blank);
}
#endif

View File

@ -32,13 +32,15 @@ using namespace winstd;
// eap::peer_tls_tunnel // eap::peer_tls_tunnel
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
eap::peer_tls_tunnel::peer_tls_tunnel(_In_ eap_type_t eap_method) : peer(eap_method) eap::peer_tls_tunnel::peer_tls_tunnel(_In_ eap_type_t eap_method) : peer_tls(eap_method)
{ {
} }
void eap::peer_tls_tunnel::initialize() void eap::peer_tls_tunnel::initialize()
{ {
peer_tls::initialize();
// MSI's feature completeness check removed: It might invoke UI (prompt user for missing MSI), // MSI's feature completeness check removed: It might invoke UI (prompt user for missing MSI),
// which would be disasterous in EapHost system service. // which would be disasterous in EapHost system service.
#if 0 #if 0
@ -60,19 +62,12 @@ void eap::peer_tls_tunnel::initialize()
void eap::peer_tls_tunnel::shutdown() void eap::peer_tls_tunnel::shutdown()
{ {
// Signal all certificate revocation verify threads to abort and wait for them (10sec max).
vector<HANDLE> chks;
chks.reserve(m_crl_checkers.size());
for (auto chk = m_crl_checkers.begin(), chk_end = m_crl_checkers.end(); chk != chk_end; ++chk) {
SetEvent(chk->m_abort);
chks.push_back(chk->m_thread);
}
WaitForMultipleObjects((DWORD)chks.size(), chks.data(), TRUE, 10000);
#if EAP_INNER_EAPHOST #if EAP_INNER_EAPHOST
// Uninitialize EapHost. It was initialized for EapHost based inner authentication methods. // Uninitialize EapHost. It was initialized for EapHost based inner authentication methods.
EapHostPeerUninitialize(); EapHostPeerUninitialize();
#endif #endif
peer_tls::shutdown();
} }
@ -378,17 +373,6 @@ void eap::peer_tls_tunnel::set_response_attributes(
} }
void eap::peer_tls_tunnel::spawn_crl_check(_Inout_ winstd::cert_context &&cert)
{
// Create the thread and add it to the list.
m_crl_checkers.push_back(std::move(crl_checker(*this, std::move(cert))));
// Now the thread is in-place, start it.
crl_checker &chk = m_crl_checkers.back();
chk.m_thread = CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(crl_checker::verify), &chk, 0, NULL);
}
_Success_(return != 0) const eap::config_method_tls_tunnel* eap::peer_tls_tunnel::combine_credentials( _Success_(return != 0) const eap::config_method_tls_tunnel* eap::peer_tls_tunnel::combine_credentials(
_In_ DWORD dwFlags, _In_ DWORD dwFlags,
_In_ const config_connection &cfg, _In_ const config_connection &cfg,
@ -515,146 +499,6 @@ eap::peer_tls_tunnel::session::~session()
} }
//////////////////////////////////////////////////////////////////////
// eap::peer_tls_tunnel::crl_checker
//////////////////////////////////////////////////////////////////////
eap::peer_tls_tunnel::crl_checker::crl_checker(_In_ module &mod, _Inout_ winstd::cert_context &&cert) :
m_module(mod),
m_cert (std::move(cert)),
m_abort (CreateEvent(NULL, TRUE, FALSE, NULL))
{
}
eap::peer_tls_tunnel::crl_checker::crl_checker(_Inout_ crl_checker &&other) noexcept :
m_module( other.m_module ),
m_thread(std::move(other.m_thread)),
m_abort (std::move(other.m_abort )),
m_cert (std::move(other.m_cert ))
{
}
eap::peer_tls_tunnel::crl_checker& eap::peer_tls_tunnel::crl_checker::operator=(_Inout_ crl_checker &&other) noexcept
{
if (this != std::addressof(other)) {
assert(std::addressof(m_module) == std::addressof(other.m_module)); // Move threads within same module only!
m_thread = std::move(other.m_thread);
m_abort = std::move(other.m_abort );
m_cert = std::move(other.m_cert );
}
return *this;
}
DWORD WINAPI eap::peer_tls_tunnel::crl_checker::verify(_In_ crl_checker *obj)
{
// Initialize COM.
com_initializer com_init(NULL);
// Wait for 5sec for the link to become online. (Hopefuly!)
if (WaitForSingleObject(obj->m_abort, 5000) == WAIT_OBJECT_0) {
// Aborted.
return 1;
}
// Prepare a list of certificates forming certificate chain.
list<cert_context> context_data;
for (cert_context c(obj->m_cert); c;) {
context_data.push_back(std::move(c));
DWORD flags = 0;
c = CertGetIssuerCertificateFromStore(obj->m_cert->hCertStore, context_data.back(), NULL, &flags);
if (!c) break;
}
// Create an array of pointers to CERT_CONTEXT required by CertVerifyRevocation().
vector<PCERT_CONTEXT> context;
context.reserve(context_data.size());
for (auto c = context_data.cbegin(), c_end = context_data.cend(); c != c_end; ++c)
context.push_back(const_cast<PCERT_CONTEXT>(c->operator PCCERT_CONTEXT()));
CERT_REVOCATION_STATUS status_rev = { sizeof(CERT_REVOCATION_STATUS) };
for (auto c = context.begin(), c_end = context.end(); c != c_end;) {
// Check for thread abort signal.
if (WaitForSingleObject(obj->m_abort, 0) == WAIT_OBJECT_0)
return 1;
// Perform revocation check.
if (!CertVerifyRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE,
(DWORD)(c_end - c), reinterpret_cast<PVOID*>(&*c),
CERT_VERIFY_REV_CHAIN_FLAG, NULL, &status_rev))
{
PCCERT_CONTEXT cert = *(c + status_rev.dwIndex);
wstring subj;
if (!CertGetNameStringW(cert, CERT_NAME_DNS_TYPE, CERT_NAME_STR_ENABLE_PUNYCODE_FLAG, NULL, subj))
sprintf(subj, L"(error %u)", GetLastError());
switch (status_rev.dwError) {
case CRYPT_E_NO_REVOCATION_CHECK:
// Revocation check could not be performed.
c += (size_t)status_rev.dwIndex + 1;
if (c == c_end) {
// This "error" is expected for the root CA certificate.
} else {
// This really was an error, as it appeared before the root CA cerficate in the chain.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_SKIPPED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data::blank);
}
break;
case CRYPT_E_REVOKED:
// One of the certificates in the chain was revoked.
switch (status_rev.dwReason) {
case CRL_REASON_AFFILIATION_CHANGED:
case CRL_REASON_SUPERSEDED:
case CRL_REASON_CESSATION_OF_OPERATION:
case CRL_REASON_CERTIFICATE_HOLD:
// The revocation was of administrative nature. No need to black-list.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED1, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
break;
default: {
// One of the certificates in the chain was revoked as compromised. Black-list it.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
reg_key key;
if (key.create(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\") _T(VENDOR_NAME_STR) _T("\\") _T(PRODUCT_NAME_STR) _T("\\TLSCRL"), NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE)) {
vector<unsigned char> hash;
if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, hash)) {
wstring hash_unicode;
hex_enc enc;
enc.encode(hash_unicode, hash.data(), hash.size());
RegSetValueExW(key, hash_unicode.c_str(), NULL, REG_SZ, reinterpret_cast<LPCBYTE>(subj.c_str()), (DWORD)((subj.length() + 1) * sizeof(wstring::value_type)));
}
}
}}
// Resume checking the rest of the chain.
c += (size_t)status_rev.dwIndex + 1;
break;
case ERROR_SUCCESS:
// Odd. CertVerifyRevocation() should return TRUE then. Nevertheless, we take this as a "yes".
c = c_end;
break;
default:
// Checking one of the certificates in the chain for revocation failed. Resume checking the rest.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FAILED, event_data((unsigned int)obj->m_module.m_eap_method), event_data(subj), event_data(status_rev.dwError), event_data::blank);
c += (size_t)status_rev.dwIndex + 1;
}
} else {
// Revocation check finished.
break;
}
}
// Revocation check succeeded.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FINISHED, event_data((unsigned int)obj->m_module.m_eap_method), event_data::blank);
return 0;
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// eap::peer_ttls // eap::peer_ttls
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -715,7 +559,7 @@ eap::method* eap::peer_ttls::make_method(_In_ config_method_tls_tunnel &cfg, _In
} }
return return
new method_eap (*this, eap_type_t::ttls, cred, new method_eap (*this, eap_type_t::ttls, cred,
new method_defrag (*this, 0 /* Schannel supports retrieving keying material for EAP-TTLSv0 only. */, new method_defrag(*this, 0 /* Schannel supports retrieving keying material for EAP-TTLSv0 only. */,
new method_tls_tunnel(*this, eap_type_t::ttls, cfg, cred, meth_inner.release()))); new method_tls (*this, cfg, cred, meth_inner.release())));
} }