diff --git a/lib/TLS/src/Method.cpp b/lib/TLS/src/Method.cpp index 4addcf9..24c807a 100644 --- a/lib/TLS/src/Method.cpp +++ b/lib/TLS/src/Method.cpp @@ -1,1596 +1,1596 @@ -/* - 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" - -using namespace std; -using namespace winstd; - - -////////////////////////////////////////////////////////////////////// -// eap::method_tls::packet -////////////////////////////////////////////////////////////////////// - -eap::method_tls::packet::packet() : - m_code((EapCode)0), - m_id(0), - m_flags(0) -{ -} - - -eap::method_tls::packet::packet(_In_ const packet &other) : - m_code (other.m_code ), - m_id (other.m_id ), - m_flags(other.m_flags), - m_data (other.m_data ) -{ -} - - -eap::method_tls::packet::packet(_Inout_ packet &&other) : - m_code (std::move(other.m_code )), - m_id (std::move(other.m_id )), - m_flags(std::move(other.m_flags)), - m_data (std::move(other.m_data )) -{ -} - - -eap::method_tls::packet& eap::method_tls::packet::operator=(_In_ const packet &other) -{ - if (this != std::addressof(other)) { - m_code = other.m_code ; - m_id = other.m_id ; - m_flags = other.m_flags; - m_data = other.m_data ; - } - - return *this; -} - - -eap::method_tls::packet& eap::method_tls::packet::operator=(_Inout_ packet &&other) -{ - if (this != std::addressof(other)) { - m_code = std::move(other.m_code ); - m_id = std::move(other.m_id ); - m_flags = std::move(other.m_flags); - m_data = std::move(other.m_data ); - } - - return *this; -} - - -void eap::method_tls::packet::clear() -{ - m_code = (EapCode)0; - m_id = 0; - m_flags = 0; - m_data.clear(); -} - - -////////////////////////////////////////////////////////////////////// -// eap::method_tls -////////////////////////////////////////////////////////////////////// - -eap::method_tls::method_tls(_In_ module &module, _In_ config_provider_list &cfg, _In_ credentials_tls &cred) : - m_cred(cred), - m_phase(phase_unknown), - m_seq_num_client(0), - m_seq_num_server(0), - m_blob_cfg(NULL), -#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE - m_blob_cred(NULL), -#endif - method(module, cfg, cred) -{ - m_tls_version = tls_version_1_2; - memset(m_handshake, 0, sizeof(m_handshake)); -} - - -eap::method_tls::method_tls(_Inout_ method_tls &&other) : - m_cred ( other.m_cred ), - m_packet_req (std::move(other.m_packet_req )), - m_packet_res (std::move(other.m_packet_res )), - 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_key_mppe_client (std::move(other.m_key_mppe_client )), - m_key_mppe_server (std::move(other.m_key_mppe_server )), - m_session_id (std::move(other.m_session_id )), - 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_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 )), - method (std::move(other )) -{ - memcpy(m_handshake, other.m_handshake, sizeof(m_handshake)); -#ifdef _DEBUG - memset(other.m_handshake, 0, sizeof(m_handshake)); -#endif -} - - -eap::method_tls::~method_tls() -{ - if (m_blob_cfg) - m_module.free_memory(m_blob_cfg); - -#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE - if (m_blob_cred) - m_module.free_memory(m_blob_cred); -#endif -} - - -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_packet_req = std::move(other.m_packet_req ); - m_packet_res = std::move(other.m_packet_res ); - 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_key_mppe_client = std::move(other.m_key_mppe_client ); - m_key_mppe_server = std::move(other.m_key_mppe_server ); - m_session_id = std::move(other.m_session_id ); - 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_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 ); - - memcpy(m_handshake, other.m_handshake, sizeof(m_handshake)); -#ifdef _DEBUG - memset(other.m_handshake, 0, sizeof(m_handshake)); -#endif - } - - return *this; -} - - -void eap::method_tls::begin_session( - _In_ DWORD dwFlags, - _In_ const EapAttributes *pAttributeArray, - _In_ HANDLE hTokenImpersonateUser, - _In_ DWORD dwMaxSendPacketSize) -{ - method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); - - // Create cryptographics provider for support needs (handshake hashing, client random, temporary keys...). - if (!m_cp.create(NULL, NULL, PROV_RSA_AES)) - 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."); - - if (m_cfg.m_providers.empty() || m_cfg.m_providers.front().m_methods.empty()) - throw invalid_argument(__FUNCTION__ " Configuration has no providers and/or methods."); - - const config_provider &cfg_prov(m_cfg.m_providers.front()); - const config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); - assert(cfg_method); - - // Restore previous session ID and master secret. We might get lucky. - m_session_id = cfg_method->m_session_id; - m_master_secret = cfg_method->m_master_secret; -} - - -void eap::method_tls::process_request_packet( - _In_bytecount_(dwReceivedPacketSize) const EapPacket *pReceivedPacket, - _In_ DWORD dwReceivedPacketSize, - _Inout_ EapPeerMethodOutput *pEapOutput) -{ - assert(pReceivedPacket && dwReceivedPacketSize >= 4); - assert(pEapOutput); - - // 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."); - //else if (pReceivedPacket->Data[0] != eap_type_tls) // Skip method check, to allow TTLS extension. - // throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " Packet is not EAP-TLS (expected: %u, received: %u).", eap_type_tls, pReceivedPacket->Data[0])); - - // Get packet data pointer and size for more readable code later on. - const unsigned char *packet_data_ptr; - size_t packet_data_size; - if (pReceivedPacket->Data[1] & flags_req_length_incl) { - // Length field is included. - packet_data_ptr = pReceivedPacket->Data + 6; - packet_data_size = dwReceivedPacketSize - 10; - } else { - // Length field not included. - packet_data_ptr = pReceivedPacket->Data + 2; - packet_data_size = dwReceivedPacketSize - 6; - } - - // Do the EAP-TLS defragmentation. - if (pReceivedPacket->Data[1] & flags_req_more_frag) { - if (m_packet_req.m_data.empty()) { - // Start a new packet. - if (pReceivedPacket->Data[1] & flags_req_length_incl) { - // Preallocate data according to the Length field. - size_t size_tot = ntohl(*(unsigned int*)(pReceivedPacket->Data + 2)); - m_packet_req.m_data.reserve(size_tot); - m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_FIRST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)size_tot), event_data::blank); - } else { - // The Length field was not included. Odd. Nevermind, no pre-allocation then. - m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_FIRST1, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data::blank); - } - } else { - // Mid fragment received. - m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_MID, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)m_packet_req.m_data.size()), event_data::blank); - } - m_packet_req.m_data.insert(m_packet_req.m_data.end(), packet_data_ptr, packet_data_ptr + packet_data_size); - - // Reply with ACK packet. - m_packet_res.m_code = EapCodeResponse; - m_packet_res.m_id = pReceivedPacket->Id; - m_packet_res.m_flags = 0; - m_packet_res.m_data.clear(); - pEapOutput->fAllowNotifications = FALSE; - pEapOutput->action = EapPeerMethodResponseActionSend; - return; - } else if (!m_packet_req.m_data.empty()) { - // Last fragment received. Append data. - m_packet_req.m_data.insert(m_packet_req.m_data.end(), packet_data_ptr, packet_data_ptr + packet_data_size); - m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_LAST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)m_packet_req.m_data.size()), event_data::blank); - } else { - // This is a complete non-fragmented packet. - m_packet_req.m_data.assign(packet_data_ptr, packet_data_ptr + packet_data_size); - m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data::blank); - } - - m_packet_req.m_code = (EapCode)pReceivedPacket->Code; - m_packet_req.m_id = pReceivedPacket->Id; - m_packet_req.m_flags = pReceivedPacket->Data[1]; - - if (m_packet_res.m_flags & flags_res_more_frag) { - // We are sending a fragmented message. - if ( m_packet_req.m_code == EapCodeRequest && - m_packet_req.m_id == m_packet_res.m_id && - m_packet_req.m_data.empty() && - !(m_packet_req.m_flags & (flags_req_length_incl | flags_req_more_frag | flags_req_start))) - { - // This is the ACK of our fragmented message packet. Send the next fragment. - m_packet_res.m_id++; - pEapOutput->fAllowNotifications = FALSE; - pEapOutput->action = EapPeerMethodResponseActionSend; - return; - } 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; - - if (pReceivedPacket->Code == EapCodeRequest && (m_packet_req.m_flags & flags_req_start)) { - // This is the EAP-TLS start message: (re)initialize method. - m_module.log_event(&EAPMETHOD_TLS_HANDSHAKE_START2, event_data((unsigned int)eap_type_tls), event_data::blank); - m_phase = phase_client_hello; - } else { - // Process the packet. - memset(m_handshake, 0, sizeof(m_handshake)); - 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_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; - 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?"); - - // Create cryptographics provider (based on server selected cipher?). - if (!m_cp_enc_client.create(NULL, NULL, PROV_RSA_AES)) - 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_enc_client, 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(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); - seed.insert(seed.end(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); - memcpy(&m_master_secret, prf(m_cp_enc_client, 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()); - - // 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; - - // 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(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); - seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); - sanitizing_blob key_block(prf(m_cp_enc_client, 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_enc_client, 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()); - - m_phase = m_handshake[tls_handshake_type_finished] ? phase_application_data : phase_change_cipher_spec; - 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; - break; - - case phase_application_data: - if (m_handshake[tls_handshake_type_hello_request]) - m_phase = phase_client_hello; - } - - // 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(); - - pEapOutput->fAllowNotifications = FALSE; - pEapOutput->action = EapPeerMethodResponseActionSend; -} - - -void eap::method_tls::get_response_packet( - _Inout_bytecap_(*dwSendPacketSize) EapPacket *pSendPacket, - _Inout_ DWORD *pdwSendPacketSize) -{ - assert(pdwSendPacketSize); - assert(pSendPacket); - - unsigned int - size_data = (unsigned int)m_packet_res.m_data.size(), - size_packet = size_data + 6; - unsigned short size_packet_limit = (unsigned short)std::min(*pdwSendPacketSize, USHRT_MAX); - unsigned char *data_dst; - - if (!(m_packet_res.m_flags & flags_res_more_frag)) { - // Not fragmented. - if (size_packet <= size_packet_limit) { - // No need to fragment the packet. - m_packet_res.m_flags &= ~flags_res_length_incl; // No need to explicitly include the Length field either. - data_dst = pSendPacket->Data + 2; - m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data::blank); - } else { - // But it should be fragmented. - m_packet_res.m_flags |= flags_res_length_incl | flags_res_more_frag; - *(unsigned int*)(pSendPacket->Data + 2) = (unsigned int)size_packet; - data_dst = pSendPacket->Data + 6; - size_data = size_packet_limit - 10; - size_packet = size_packet_limit; - m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_FIRST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); - } - } else { - // Continuing the fragmented packet... - if (size_packet > size_packet_limit) { - // This is a mid fragment. - m_packet_res.m_flags &= ~flags_res_length_incl; - size_data = size_packet_limit - 6; - size_packet = size_packet_limit; - m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_MID, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); - } else { - // This is the last fragment. - m_packet_res.m_flags &= ~(flags_res_length_incl | flags_res_more_frag); - m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_LAST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); - } - data_dst = pSendPacket->Data + 2; - } - - pSendPacket->Code = (BYTE)m_packet_res.m_code; - pSendPacket->Id = m_packet_res.m_id; - *(unsigned short*)pSendPacket->Length = htons((unsigned short)size_packet); - pSendPacket->Data[0] = (BYTE)eap_type_tls; - pSendPacket->Data[1] = m_packet_res.m_flags; - memcpy(data_dst, m_packet_res.m_data.data(), size_data); - m_packet_res.m_data.erase(m_packet_res.m_data.begin(), m_packet_res.m_data.begin() + size_data); - *pdwSendPacketSize = size_packet; -} - - -void eap::method_tls::get_result( - _In_ EapPeerMethodResultReason reason, - _Inout_ EapPeerMethodResult *ppResult) -{ - assert(ppResult); - - config_provider &cfg_prov(m_cfg.m_providers.front()); - config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); - assert(cfg_method); - - switch (reason) { - case EapPeerMethodResultSuccess: { - if (!m_handshake[tls_handshake_type_finished]) - throw invalid_argument(__FUNCTION__ " Premature success."); - - m_module.log_event(&EAPMETHOD_TLS_SUCCESS, event_data((unsigned int)eap_type_tls), event_data::blank); - - // Derive MSK/EMSK for line encryption. - derive_msk(); - - // Fill array with RADIUS attributes. - eap_attr a; - m_eap_attr.clear(); - m_eap_attr.reserve(3); - a.create_ms_mppe_key(16, (LPCBYTE)&m_key_mppe_client, sizeof(tls_random)); - m_eap_attr.push_back(std::move(a)); - a.create_ms_mppe_key(17, (LPCBYTE)&m_key_mppe_server, sizeof(tls_random)); - m_eap_attr.push_back(std::move(a)); - 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(); - ppResult->pAttribArray = &m_eap_attr_desc; - - // Clear credentials as failed. - cfg_method->m_auth_failed = false; - - ppResult->fIsSuccess = TRUE; - ppResult->dwFailureReasonCode = ERROR_SUCCESS; - - // Update configuration with session resumption data and prepare BLOB. - cfg_method->m_session_id = m_session_id; - cfg_method->m_master_secret = m_master_secret; - - break; - } - - case EapPeerMethodResultFailure: - m_module.log_event(&EAPMETHOD_TLS_FAILURE, event_data((unsigned int)eap_type_tls), event_data::blank); - - // Clear session resumption data. - cfg_method->m_session_id.clear(); - cfg_method->m_master_secret.clear(); - - // Mark credentials as failed, so GUI can re-prompt user. - cfg_method->m_auth_failed = true; - - // 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. - //ppResult->fIsSuccess = FALSE; - //ppResult->dwFailureReasonCode = EAP_E_AUTHENTICATION_FAILED; - - break; - - default: - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); - } - - // Always ask EAP host to save the connection data. - ppResult->fSaveConnectionData = TRUE; - m_module.pack(m_cfg, &ppResult->pConnectionData, &ppResult->dwSizeofConnectionData); - if (m_blob_cfg) - m_module.free_memory(m_blob_cfg); - m_blob_cfg = ppResult->pConnectionData; - -#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE - ppResult->fSaveUserData = TRUE; - m_module.pack(m_cred, &ppResult->pUserData, &ppResult->dwSizeofUserData); - if (m_blob_cred) - m_module.free_memory(m_blob_cred); - m_blob_cred = ppResult->pUserData; -#endif -} - - -eap::sanitizing_blob eap::method_tls::make_client_hello() -{ - static const unsigned char s_cipher_suite[] = { - 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 - }; - - 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(), (unsigned char*)&ssl_header, (unsigned char*)(&ssl_header + 1)); - - // SSL version - msg.insert(msg.end(), (unsigned char*)&m_tls_version, (unsigned char*)(&m_tls_version + 1)); - - // Generate client random and add it to the message - m_random_client.randomize(m_cp); - msg.insert(msg.end(), (unsigned char*)&m_random_client, (unsigned char*)(&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(), (unsigned char*)&size_cipher_suite2, (unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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((const unsigned char*)&pms, (const unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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_enc_client, 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((const unsigned char*)&hdr, (const unsigned char*)(&hdr + 1)); - msg.insert(msg.end(), data.begin(), data.end()); - return msg; -} - - -void eap::method_tls::derive_msk() -{ - static const unsigned char s_label[] = "client EAP encryption"; - sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); - seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); - seed.insert(seed.end(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); - sanitizing_blob key_block(prf(m_cp_enc_client, m_alg_prf, m_master_secret, seed, 2*sizeof(tls_random))); - const unsigned char *_key_block = key_block.data(); - - // 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::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) -{ - sanitizing_blob data; - - for (const unsigned char *pck = (const unsigned char*)_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 = (const unsigned char*)(hdr + 1), - *msg_end = msg + ntohs(*(unsigned short*)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; - } - - //default: - // if (m_state_server.m_alg_encrypt) { - // sanitizing_blob msg_dec(msg, msg_end); - // decrypt_message(hdr->type, msg_dec); - // process_vendor_data(hdr->type, msg_dec.data(), msg_dec.size()); - // } else - // process_vendor_data(hdr->type, msg, msg_end - msg); - } - } - - pck = msg_end; - } -} - - -void eap::method_tls::process_change_cipher_spec(_In_bytecount_(msg_size) const void *_msg, _In_ size_t msg_size) -{ - if (msg_size < 1) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete change cipher spec."); - - const unsigned char *msg = (const unsigned char*)_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, NULL, PROV_RSA_AES)) - 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(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); - seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); - sanitizing_blob key_block(prf(m_cp_enc_server, 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_enc_server, 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_(msg_size) const void *_msg, _In_ size_t msg_size) -{ - if (msg_size < 2) - throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete alert."); - - const unsigned char *msg = (const unsigned char*)_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_(msg_size) const void *_msg, _In_ size_t msg_size) -{ - for (const unsigned char *msg = (const unsigned char*)_msg, *msg_end = msg + msg_size; 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(*(unsigned int*)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 (*(tls_version*)rec < tls_version_1_0 || m_tls_version < *(tls_version*)rec) - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Unsupported SSL/TLS version."); - m_tls_version = *(tls_version*)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 or incomplete."); - if (rec[0] == 0x00 || rec[1] == 0x2f) { - // TLS_RSA_WITH_AES_128_CBC_SHA - m_state_server_pending.m_alg_encrypt = CALG_AES_128; - m_state_server_pending.m_size_enc_key = 128/8; // AES-128 - m_state_server_pending.m_size_enc_iv = 128/8; // AES-128 - m_state_server_pending.m_size_enc_block = 128/8; // AES-128 - m_state_server_pending.m_alg_mac = CALG_SHA1; - m_state_server_pending.m_size_mac_key = 160/8; // SHA-1 - m_state_server_pending.m_size_mac_hash = 160/8; // SHA-1 - } else if (rec[0] == 0x00 || rec[1] == 0x0a) { - // TLS_RSA_WITH_3DES_EDE_CBC_SHA - m_state_server_pending.m_alg_encrypt = CALG_3DES; - m_state_server_pending.m_size_enc_key = 192/8; // 3DES 192bits - m_state_server_pending.m_size_enc_iv = 64/8; // 3DES 64bits - m_state_server_pending.m_size_enc_block = 64/8; // 3DES 64bits - m_state_server_pending.m_alg_mac = CALG_SHA1; - m_state_server_pending.m_size_mac_key = 160/8; // SHA-1 - m_state_server_pending.m_size_mac_hash = 160/8; // SHA-1 - } 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_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_enc_server, 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[type] = true; - } - - 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; - } -} - - -void eap::method_tls::process_application_data(_In_bytecount_(msg_size) const void *msg, _In_ size_t msg_size) -{ - UNREFERENCED_PARAMETER(msg); - UNREFERENCED_PARAMETER(msg_size); - - // TODO: Parse application data (Diameter AVP) -} - - -//void eap::method_tls::process_vendor_data(_In_ tls_message_type_t type, _In_bytecount_(msg_size) const void *msg, _In_ size_t msg_size) -//{ -// UNREFERENCED_PARAMETER(type); -// UNREFERENCED_PARAMETER(msg); -// UNREFERENCED_PARAMETER(msg_size); -//} - - -void eap::method_tls::verify_server_trust() const -{ - assert(!m_server_cert_chain.empty()); - const cert_context &cert = m_server_cert_chain.front(); - - string subj; - if (!CertGetNameStringA(cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, subj)) - throw win_runtime_error(__FUNCTION__ " Error retrieving server's certificate subject name."); - - const config_provider &cfg_prov(m_cfg.m_providers.front()); - const config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); - assert(cfg_method); - - if (!cfg_method->m_server_names.empty()) { - // Check server name. - for (list::const_iterator s = cfg_method->m_server_names.cbegin(), s_end = cfg_method->m_server_names.cend();; ++s) { - if (s != s_end) { - const char - *a = s->c_str(), - *b = subj.c_str(); - size_t - len_a = s->length(), - len_b = subj.length(); - - if (_stricmp(a, b) == 0 || // Direct match - a[0] == '*' && len_b + 1 >= len_a && _stricmp(a + 1, b + len_b - (len_a - 1)) == 0) // "*..." wildchar match - { - m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED, event_data(subj), event_data::blank); - break; - } - } else - throw win_runtime_error(ERROR_INVALID_DOMAINNAME, string_printf(__FUNCTION__ " Server name %s is not on the list of trusted server names.", subj.c_str()).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 com_runtime_error(CRYPT_E_SELF_SIGNED, string_printf(__FUNCTION__ " Server is using a self-signed certificate %s. Cannot trust it.", subj.c_str()).c_str()); - - // 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(ERROR_INVALID_DOMAINNAME, __FUNCTION__ " Error creating temporary certificate store."); - for (list::const_iterator c = cfg_method->m_trusted_root_ca.cbegin(), c_end = cfg_method->m_trusted_root_ca.cend(); c != c_end; ++c) - CertAddCertificateContextToStore(store, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); - - // Add all certificates from the server's certificate chain, except the first one. - for (list::const_iterator 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); - } - - // 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 when we check root CA explicitly. - if (context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR && - (cfg_method->m_trusted_root_ca.empty() || (context->TrustStatus.dwErrorStatus & ~CERT_TRUST_IS_UNTRUSTED_ROOT) != CERT_TRUST_NO_ERROR)) - throw win_runtime_error(context->TrustStatus.dwErrorStatus, "Error validating certificate chain."); - - if (!cfg_method->m_trusted_root_ca.empty()) { - // Verify Root CA against our trusted root CA list - if (context->cChain != 1) - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Multiple chain verification not supported."); - if (context->rgpChain[0]->cElement == 0) - throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Can not verify empty certificate chain."); - - PCCERT_CONTEXT cert_root = context->rgpChain[0]->rgpElement[context->rgpChain[0]->cElement-1]->pCertContext; - for (list::const_iterator c = cfg_method->m_trusted_root_ca.cbegin(), c_end = cfg_method->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 win_runtime_error(ERROR_FILE_NOT_FOUND, __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); -} - - -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_enc_client, 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_enc_client, (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_enc_server, 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++; - } -} - - -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 = (const unsigned char*)&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((unsigned char*)seed, (unsigned char*)seed + size_seed), - A2((unsigned char*)seed, (unsigned char*)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((unsigned char*)seed, (unsigned char*)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((const unsigned char*)&prefix, (const unsigned char*)(&prefix + 1)); - key_blob.insert(key_blob.end(), (const unsigned char*)secret, (const unsigned char*)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((const unsigned char*)&prefix, (const unsigned char*)(&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(), (const unsigned char*)secret, (const unsigned char*)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 -} +/* + 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" + +using namespace std; +using namespace winstd; + + +////////////////////////////////////////////////////////////////////// +// eap::method_tls::packet +////////////////////////////////////////////////////////////////////// + +eap::method_tls::packet::packet() : + m_code((EapCode)0), + m_id(0), + m_flags(0) +{ +} + + +eap::method_tls::packet::packet(_In_ const packet &other) : + m_code (other.m_code ), + m_id (other.m_id ), + m_flags(other.m_flags), + m_data (other.m_data ) +{ +} + + +eap::method_tls::packet::packet(_Inout_ packet &&other) : + m_code (std::move(other.m_code )), + m_id (std::move(other.m_id )), + m_flags(std::move(other.m_flags)), + m_data (std::move(other.m_data )) +{ +} + + +eap::method_tls::packet& eap::method_tls::packet::operator=(_In_ const packet &other) +{ + if (this != std::addressof(other)) { + m_code = other.m_code ; + m_id = other.m_id ; + m_flags = other.m_flags; + m_data = other.m_data ; + } + + return *this; +} + + +eap::method_tls::packet& eap::method_tls::packet::operator=(_Inout_ packet &&other) +{ + if (this != std::addressof(other)) { + m_code = std::move(other.m_code ); + m_id = std::move(other.m_id ); + m_flags = std::move(other.m_flags); + m_data = std::move(other.m_data ); + } + + return *this; +} + + +void eap::method_tls::packet::clear() +{ + m_code = (EapCode)0; + m_id = 0; + m_flags = 0; + m_data.clear(); +} + + +////////////////////////////////////////////////////////////////////// +// eap::method_tls +////////////////////////////////////////////////////////////////////// + +eap::method_tls::method_tls(_In_ module &module, _In_ config_provider_list &cfg, _In_ credentials_tls &cred) : + m_cred(cred), + m_phase(phase_unknown), + m_seq_num_client(0), + m_seq_num_server(0), + m_blob_cfg(NULL), +#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE + m_blob_cred(NULL), +#endif + method(module, cfg, cred) +{ + m_tls_version = tls_version_1_2; + memset(m_handshake, 0, sizeof(m_handshake)); +} + + +eap::method_tls::method_tls(_Inout_ method_tls &&other) : + m_cred ( other.m_cred ), + m_packet_req (std::move(other.m_packet_req )), + m_packet_res (std::move(other.m_packet_res )), + 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_key_mppe_client (std::move(other.m_key_mppe_client )), + m_key_mppe_server (std::move(other.m_key_mppe_server )), + m_session_id (std::move(other.m_session_id )), + 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_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 )), + method (std::move(other )) +{ + memcpy(m_handshake, other.m_handshake, sizeof(m_handshake)); +#ifdef _DEBUG + memset(other.m_handshake, 0, sizeof(m_handshake)); +#endif +} + + +eap::method_tls::~method_tls() +{ + if (m_blob_cfg) + m_module.free_memory(m_blob_cfg); + +#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE + if (m_blob_cred) + m_module.free_memory(m_blob_cred); +#endif +} + + +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_packet_req = std::move(other.m_packet_req ); + m_packet_res = std::move(other.m_packet_res ); + 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_key_mppe_client = std::move(other.m_key_mppe_client ); + m_key_mppe_server = std::move(other.m_key_mppe_server ); + m_session_id = std::move(other.m_session_id ); + 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_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 ); + + memcpy(m_handshake, other.m_handshake, sizeof(m_handshake)); +#ifdef _DEBUG + memset(other.m_handshake, 0, sizeof(m_handshake)); +#endif + } + + return *this; +} + + +void eap::method_tls::begin_session( + _In_ DWORD dwFlags, + _In_ const EapAttributes *pAttributeArray, + _In_ HANDLE hTokenImpersonateUser, + _In_ DWORD dwMaxSendPacketSize) +{ + method::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + + // Create cryptographics provider for support needs (handshake hashing, client random, temporary keys...). + if (!m_cp.create(NULL, NULL, PROV_RSA_AES)) + 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."); + + if (m_cfg.m_providers.empty() || m_cfg.m_providers.front().m_methods.empty()) + throw invalid_argument(__FUNCTION__ " Configuration has no providers and/or methods."); + + const config_provider &cfg_prov(m_cfg.m_providers.front()); + const config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); + assert(cfg_method); + + // Restore previous session ID and master secret. We might get lucky. + m_session_id = cfg_method->m_session_id; + m_master_secret = cfg_method->m_master_secret; +} + + +void eap::method_tls::process_request_packet( + _In_bytecount_(dwReceivedPacketSize) const EapPacket *pReceivedPacket, + _In_ DWORD dwReceivedPacketSize, + _Inout_ EapPeerMethodOutput *pEapOutput) +{ + assert(pReceivedPacket && dwReceivedPacketSize >= 4); + assert(pEapOutput); + + // 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."); + //else if (pReceivedPacket->Data[0] != eap_type_tls) // Skip method check, to allow TTLS extension. + // throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, string_printf(__FUNCTION__ " Packet is not EAP-TLS (expected: %u, received: %u).", eap_type_tls, pReceivedPacket->Data[0])); + + // Get packet data pointer and size for more readable code later on. + const unsigned char *packet_data_ptr; + size_t packet_data_size; + if (pReceivedPacket->Data[1] & flags_req_length_incl) { + // Length field is included. + packet_data_ptr = pReceivedPacket->Data + 6; + packet_data_size = dwReceivedPacketSize - 10; + } else { + // Length field not included. + packet_data_ptr = pReceivedPacket->Data + 2; + packet_data_size = dwReceivedPacketSize - 6; + } + + // Do the EAP-TLS defragmentation. + if (pReceivedPacket->Data[1] & flags_req_more_frag) { + if (m_packet_req.m_data.empty()) { + // Start a new packet. + if (pReceivedPacket->Data[1] & flags_req_length_incl) { + // Preallocate data according to the Length field. + size_t size_tot = ntohl(*(unsigned int*)(pReceivedPacket->Data + 2)); + m_packet_req.m_data.reserve(size_tot); + m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_FIRST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)size_tot), event_data::blank); + } else { + // The Length field was not included. Odd. Nevermind, no pre-allocation then. + m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_FIRST1, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data::blank); + } + } else { + // Mid fragment received. + m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_MID, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)m_packet_req.m_data.size()), event_data::blank); + } + m_packet_req.m_data.insert(m_packet_req.m_data.end(), packet_data_ptr, packet_data_ptr + packet_data_size); + + // Reply with ACK packet. + m_packet_res.m_code = EapCodeResponse; + m_packet_res.m_id = pReceivedPacket->Id; + m_packet_res.m_flags = 0; + m_packet_res.m_data.clear(); + pEapOutput->fAllowNotifications = FALSE; + pEapOutput->action = EapPeerMethodResponseActionSend; + return; + } else if (!m_packet_req.m_data.empty()) { + // Last fragment received. Append data. + m_packet_req.m_data.insert(m_packet_req.m_data.end(), packet_data_ptr, packet_data_ptr + packet_data_size); + m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV_FRAG_LAST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data((unsigned int)m_packet_req.m_data.size()), event_data::blank); + } else { + // This is a complete non-fragmented packet. + m_packet_req.m_data.assign(packet_data_ptr, packet_data_ptr + packet_data_size); + m_module.log_event(&EAPMETHOD_TLS_PACKET_RECV, event_data((unsigned int)eap_type_tls), event_data((unsigned int)packet_data_size), event_data::blank); + } + + m_packet_req.m_code = (EapCode)pReceivedPacket->Code; + m_packet_req.m_id = pReceivedPacket->Id; + m_packet_req.m_flags = pReceivedPacket->Data[1]; + + if (m_packet_res.m_flags & flags_res_more_frag) { + // We are sending a fragmented message. + if ( m_packet_req.m_code == EapCodeRequest && + m_packet_req.m_id == m_packet_res.m_id && + m_packet_req.m_data.empty() && + !(m_packet_req.m_flags & (flags_req_length_incl | flags_req_more_frag | flags_req_start))) + { + // This is the ACK of our fragmented message packet. Send the next fragment. + m_packet_res.m_id++; + pEapOutput->fAllowNotifications = FALSE; + pEapOutput->action = EapPeerMethodResponseActionSend; + return; + } 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; + + if (pReceivedPacket->Code == EapCodeRequest && (m_packet_req.m_flags & flags_req_start)) { + // This is the EAP-TLS start message: (re)initialize method. + m_module.log_event(&EAPMETHOD_TLS_HANDSHAKE_START2, event_data((unsigned int)eap_type_tls), event_data::blank); + m_phase = phase_client_hello; + } else { + // Process the packet. + memset(m_handshake, 0, sizeof(m_handshake)); + 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_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; + 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?"); + + // Create cryptographics provider (based on server selected cipher?). + if (!m_cp_enc_client.create(NULL, NULL, PROV_RSA_AES)) + 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_enc_client, 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(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); + seed.insert(seed.end(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); + memcpy(&m_master_secret, prf(m_cp_enc_client, 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()); + + // 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; + + // 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(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); + seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); + sanitizing_blob key_block(prf(m_cp_enc_client, 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_enc_client, 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()); + + m_phase = m_handshake[tls_handshake_type_finished] ? phase_application_data : phase_change_cipher_spec; + 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; + break; + + case phase_application_data: + if (m_handshake[tls_handshake_type_hello_request]) + m_phase = phase_client_hello; + } + + // 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(); + + pEapOutput->fAllowNotifications = FALSE; + pEapOutput->action = EapPeerMethodResponseActionSend; +} + + +void eap::method_tls::get_response_packet( + _Inout_bytecap_(*dwSendPacketSize) EapPacket *pSendPacket, + _Inout_ DWORD *pdwSendPacketSize) +{ + assert(pdwSendPacketSize); + assert(pSendPacket); + + unsigned int + size_data = (unsigned int)m_packet_res.m_data.size(), + size_packet = size_data + 6; + unsigned short size_packet_limit = (unsigned short)std::min(*pdwSendPacketSize, USHRT_MAX); + unsigned char *data_dst; + + if (!(m_packet_res.m_flags & flags_res_more_frag)) { + // Not fragmented. + if (size_packet <= size_packet_limit) { + // No need to fragment the packet. + m_packet_res.m_flags &= ~flags_res_length_incl; // No need to explicitly include the Length field either. + data_dst = pSendPacket->Data + 2; + m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data::blank); + } else { + // But it should be fragmented. + m_packet_res.m_flags |= flags_res_length_incl | flags_res_more_frag; + *(unsigned int*)(pSendPacket->Data + 2) = (unsigned int)size_packet; + data_dst = pSendPacket->Data + 6; + size_data = size_packet_limit - 10; + size_packet = size_packet_limit; + m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_FIRST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); + } + } else { + // Continuing the fragmented packet... + if (size_packet > size_packet_limit) { + // This is a mid fragment. + m_packet_res.m_flags &= ~flags_res_length_incl; + size_data = size_packet_limit - 6; + size_packet = size_packet_limit; + m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_MID, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); + } else { + // This is the last fragment. + m_packet_res.m_flags &= ~(flags_res_length_incl | flags_res_more_frag); + m_module.log_event(&EAPMETHOD_TLS_PACKET_SEND_FRAG_LAST, event_data((unsigned int)eap_type_tls), event_data((unsigned int)size_data), event_data((unsigned int)(m_packet_res.m_data.size() - size_data)), event_data::blank); + } + data_dst = pSendPacket->Data + 2; + } + + pSendPacket->Code = (BYTE)m_packet_res.m_code; + pSendPacket->Id = m_packet_res.m_id; + *(unsigned short*)pSendPacket->Length = htons((unsigned short)size_packet); + pSendPacket->Data[0] = (BYTE)eap_type_tls; + pSendPacket->Data[1] = m_packet_res.m_flags; + memcpy(data_dst, m_packet_res.m_data.data(), size_data); + m_packet_res.m_data.erase(m_packet_res.m_data.begin(), m_packet_res.m_data.begin() + size_data); + *pdwSendPacketSize = size_packet; +} + + +void eap::method_tls::get_result( + _In_ EapPeerMethodResultReason reason, + _Inout_ EapPeerMethodResult *ppResult) +{ + assert(ppResult); + + config_provider &cfg_prov(m_cfg.m_providers.front()); + config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); + assert(cfg_method); + + switch (reason) { + case EapPeerMethodResultSuccess: { + if (!m_handshake[tls_handshake_type_finished]) + throw invalid_argument(__FUNCTION__ " Premature success."); + + m_module.log_event(&EAPMETHOD_TLS_SUCCESS, event_data((unsigned int)eap_type_tls), event_data::blank); + + // Derive MSK/EMSK for line encryption. + derive_msk(); + + // Fill array with RADIUS attributes. + eap_attr a; + m_eap_attr.clear(); + m_eap_attr.reserve(3); + a.create_ms_mppe_key(16, (LPCBYTE)&m_key_mppe_client, sizeof(tls_random)); + m_eap_attr.push_back(std::move(a)); + a.create_ms_mppe_key(17, (LPCBYTE)&m_key_mppe_server, sizeof(tls_random)); + m_eap_attr.push_back(std::move(a)); + 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(); + ppResult->pAttribArray = &m_eap_attr_desc; + + // Clear credentials as failed. + cfg_method->m_auth_failed = false; + + ppResult->fIsSuccess = TRUE; + ppResult->dwFailureReasonCode = ERROR_SUCCESS; + + // Update configuration with session resumption data and prepare BLOB. + cfg_method->m_session_id = m_session_id; + cfg_method->m_master_secret = m_master_secret; + + break; + } + + case EapPeerMethodResultFailure: + m_module.log_event(&EAPMETHOD_TLS_FAILURE, event_data((unsigned int)eap_type_tls), event_data::blank); + + // Clear session resumption data. + cfg_method->m_session_id.clear(); + cfg_method->m_master_secret.clear(); + + // Mark credentials as failed, so GUI can re-prompt user. + cfg_method->m_auth_failed = true; + + // 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. + //ppResult->fIsSuccess = FALSE; + //ppResult->dwFailureReasonCode = EAP_E_AUTHENTICATION_FAILED; + + break; + + default: + throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Not supported."); + } + + // Always ask EAP host to save the connection data. + ppResult->fSaveConnectionData = TRUE; + m_module.pack(m_cfg, &ppResult->pConnectionData, &ppResult->dwSizeofConnectionData); + if (m_blob_cfg) + m_module.free_memory(m_blob_cfg); + m_blob_cfg = ppResult->pConnectionData; + +#ifdef EAP_USE_NATIVE_CREDENTIAL_CACHE + ppResult->fSaveUserData = TRUE; + m_module.pack(m_cred, &ppResult->pUserData, &ppResult->dwSizeofUserData); + if (m_blob_cred) + m_module.free_memory(m_blob_cred); + m_blob_cred = ppResult->pUserData; +#endif +} + + +eap::sanitizing_blob eap::method_tls::make_client_hello() +{ + static const unsigned char s_cipher_suite[] = { + 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 + }; + + 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(), (unsigned char*)&ssl_header, (unsigned char*)(&ssl_header + 1)); + + // SSL version + msg.insert(msg.end(), (unsigned char*)&m_tls_version, (unsigned char*)(&m_tls_version + 1)); + + // Generate client random and add it to the message + m_random_client.randomize(m_cp); + msg.insert(msg.end(), (unsigned char*)&m_random_client, (unsigned char*)(&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)/2); + msg.insert(msg.end(), (unsigned char*)&size_cipher_suite2, (unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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((const unsigned char*)&pms, (const unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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(), (unsigned char*)&ssl_header, (unsigned char*)(&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_enc_client, 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((const unsigned char*)&hdr, (const unsigned char*)(&hdr + 1)); + msg.insert(msg.end(), data.begin(), data.end()); + return msg; +} + + +void eap::method_tls::derive_msk() +{ + static const unsigned char s_label[] = "client EAP encryption"; + sanitizing_blob seed(s_label, s_label + _countof(s_label) - 1); + seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); + seed.insert(seed.end(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); + sanitizing_blob key_block(prf(m_cp_enc_client, m_alg_prf, m_master_secret, seed, 2*sizeof(tls_random))); + const unsigned char *_key_block = key_block.data(); + + // 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::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) +{ + sanitizing_blob data; + + for (const unsigned char *pck = (const unsigned char*)_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 = (const unsigned char*)(hdr + 1), + *msg_end = msg + ntohs(*(unsigned short*)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; + } + + //default: + // if (m_state_server.m_alg_encrypt) { + // sanitizing_blob msg_dec(msg, msg_end); + // decrypt_message(hdr->type, msg_dec); + // process_vendor_data(hdr->type, msg_dec.data(), msg_dec.size()); + // } else + // process_vendor_data(hdr->type, msg, msg_end - msg); + } + } + + pck = msg_end; + } +} + + +void eap::method_tls::process_change_cipher_spec(_In_bytecount_(msg_size) const void *_msg, _In_ size_t msg_size) +{ + if (msg_size < 1) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete change cipher spec."); + + const unsigned char *msg = (const unsigned char*)_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, NULL, PROV_RSA_AES)) + 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(), (const unsigned char*)&m_random_server, (const unsigned char*)(&m_random_server + 1)); + seed.insert(seed.end(), (const unsigned char*)&m_random_client, (const unsigned char*)(&m_random_client + 1)); + sanitizing_blob key_block(prf(m_cp_enc_server, 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_enc_server, 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_(msg_size) const void *_msg, _In_ size_t msg_size) +{ + if (msg_size < 2) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete alert."); + + const unsigned char *msg = (const unsigned char*)_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_(msg_size) const void *_msg, _In_ size_t msg_size) +{ + for (const unsigned char *msg = (const unsigned char*)_msg, *msg_end = msg + msg_size; 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(*(unsigned int*)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 (*(tls_version*)rec < tls_version_1_0 || m_tls_version < *(tls_version*)rec) + throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Unsupported SSL/TLS version."); + m_tls_version = *(tls_version*)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 or incomplete."); + if (rec[0] == 0x00 || rec[1] == 0x2f) { + // TLS_RSA_WITH_AES_128_CBC_SHA + m_state_server_pending.m_alg_encrypt = CALG_AES_128; + m_state_server_pending.m_size_enc_key = 128/8; // AES-128 + m_state_server_pending.m_size_enc_iv = 128/8; // AES-128 + m_state_server_pending.m_size_enc_block = 128/8; // AES-128 + m_state_server_pending.m_alg_mac = CALG_SHA1; + m_state_server_pending.m_size_mac_key = 160/8; // SHA-1 + m_state_server_pending.m_size_mac_hash = 160/8; // SHA-1 + } else if (rec[0] == 0x00 || rec[1] == 0x0a) { + // TLS_RSA_WITH_3DES_EDE_CBC_SHA + m_state_server_pending.m_alg_encrypt = CALG_3DES; + m_state_server_pending.m_size_enc_key = 192/8; // 3DES 192bits + m_state_server_pending.m_size_enc_iv = 64/8; // 3DES 64bits + m_state_server_pending.m_size_enc_block = 64/8; // 3DES 64bits + m_state_server_pending.m_alg_mac = CALG_SHA1; + m_state_server_pending.m_size_mac_key = 160/8; // SHA-1 + m_state_server_pending.m_size_mac_hash = 160/8; // SHA-1 + } 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_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_enc_server, 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[type] = true; + } + + 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; + } +} + + +void eap::method_tls::process_application_data(_In_bytecount_(msg_size) const void *msg, _In_ size_t msg_size) +{ + UNREFERENCED_PARAMETER(msg); + UNREFERENCED_PARAMETER(msg_size); + + // TODO: Parse application data (Diameter AVP) +} + + +//void eap::method_tls::process_vendor_data(_In_ tls_message_type_t type, _In_bytecount_(msg_size) const void *msg, _In_ size_t msg_size) +//{ +// UNREFERENCED_PARAMETER(type); +// UNREFERENCED_PARAMETER(msg); +// UNREFERENCED_PARAMETER(msg_size); +//} + + +void eap::method_tls::verify_server_trust() const +{ + assert(!m_server_cert_chain.empty()); + const cert_context &cert = m_server_cert_chain.front(); + + string subj; + if (!CertGetNameStringA(cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, subj)) + throw win_runtime_error(__FUNCTION__ " Error retrieving server's certificate subject name."); + + const config_provider &cfg_prov(m_cfg.m_providers.front()); + const config_method_tls *cfg_method = dynamic_cast(cfg_prov.m_methods.front().get()); + assert(cfg_method); + + if (!cfg_method->m_server_names.empty()) { + // Check server name. + for (list::const_iterator s = cfg_method->m_server_names.cbegin(), s_end = cfg_method->m_server_names.cend();; ++s) { + if (s != s_end) { + const char + *a = s->c_str(), + *b = subj.c_str(); + size_t + len_a = s->length(), + len_b = subj.length(); + + if (_stricmp(a, b) == 0 || // Direct match + a[0] == '*' && len_b + 1 >= len_a && _stricmp(a + 1, b + len_b - (len_a - 1)) == 0) // "*..." wildchar match + { + m_module.log_event(&EAPMETHOD_TLS_SERVER_NAME_TRUSTED, event_data(subj), event_data::blank); + break; + } + } else + throw win_runtime_error(ERROR_INVALID_DOMAINNAME, string_printf(__FUNCTION__ " Server name %s is not on the list of trusted server names.", subj.c_str()).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 com_runtime_error(CRYPT_E_SELF_SIGNED, string_printf(__FUNCTION__ " Server is using a self-signed certificate %s. Cannot trust it.", subj.c_str()).c_str()); + + // 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(ERROR_INVALID_DOMAINNAME, __FUNCTION__ " Error creating temporary certificate store."); + for (list::const_iterator c = cfg_method->m_trusted_root_ca.cbegin(), c_end = cfg_method->m_trusted_root_ca.cend(); c != c_end; ++c) + CertAddCertificateContextToStore(store, *c, CERT_STORE_ADD_REPLACE_EXISTING, NULL); + + // Add all certificates from the server's certificate chain, except the first one. + for (list::const_iterator 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); + } + + // 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 when we check root CA explicitly. + if (context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR && + (cfg_method->m_trusted_root_ca.empty() || (context->TrustStatus.dwErrorStatus & ~CERT_TRUST_IS_UNTRUSTED_ROOT) != CERT_TRUST_NO_ERROR)) + throw win_runtime_error(context->TrustStatus.dwErrorStatus, "Error validating certificate chain."); + + if (!cfg_method->m_trusted_root_ca.empty()) { + // Verify Root CA against our trusted root CA list + if (context->cChain != 1) + throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Multiple chain verification not supported."); + if (context->rgpChain[0]->cElement == 0) + throw win_runtime_error(ERROR_NOT_SUPPORTED, __FUNCTION__ " Can not verify empty certificate chain."); + + PCCERT_CONTEXT cert_root = context->rgpChain[0]->rgpElement[context->rgpChain[0]->cElement-1]->pCertContext; + for (list::const_iterator c = cfg_method->m_trusted_root_ca.cbegin(), c_end = cfg_method->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 win_runtime_error(ERROR_FILE_NOT_FOUND, __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); +} + + +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_enc_client, 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_enc_client, (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_enc_server, 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++; + } +} + + +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 = (const unsigned char*)&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((unsigned char*)seed, (unsigned char*)seed + size_seed), + A2((unsigned char*)seed, (unsigned char*)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((unsigned char*)seed, (unsigned char*)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((const unsigned char*)&prefix, (const unsigned char*)(&prefix + 1)); + key_blob.insert(key_blob.end(), (const unsigned char*)secret, (const unsigned char*)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((const unsigned char*)&prefix, (const unsigned char*)(&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(), (const unsigned char*)secret, (const unsigned char*)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 +}