diff --git a/lib/MSCHAPv2/build/MSCHAPv2.vcxproj b/lib/MSCHAPv2/build/MSCHAPv2.vcxproj index 4982661..5807437 100644 --- a/lib/MSCHAPv2/build/MSCHAPv2.vcxproj +++ b/lib/MSCHAPv2/build/MSCHAPv2.vcxproj @@ -82,12 +82,14 @@ + + Create Create diff --git a/lib/MSCHAPv2/build/MSCHAPv2.vcxproj.filters b/lib/MSCHAPv2/build/MSCHAPv2.vcxproj.filters index 8c96e75..8e98b26 100644 --- a/lib/MSCHAPv2/build/MSCHAPv2.vcxproj.filters +++ b/lib/MSCHAPv2/build/MSCHAPv2.vcxproj.filters @@ -23,6 +23,9 @@ Header Files + + Header Files + @@ -37,5 +40,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/lib/MSCHAPv2/include/MSCHAPv2.h b/lib/MSCHAPv2/include/MSCHAPv2.h new file mode 100644 index 0000000..78bf3d3 --- /dev/null +++ b/lib/MSCHAPv2/include/MSCHAPv2.h @@ -0,0 +1,260 @@ +/* + 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 + + +namespace eap +{ + /// + /// MSCHAPv2 Challenge + /// + struct challenge_mschapv2; + + /// + /// MSCHAPv2 Challenge Hash + /// + struct challenge_hash; + + /// + /// NT-Password Hash + /// + struct nt_password_hash; + + /// + /// NT-Response + /// + struct nt_response; + + /// + /// Authenticator Response + /// + struct authenticator_response; + + /// + /// Creates DES encryption key with given plaintext key + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] key The key (without parity bits) + /// \param[in] size Size of \p key (maximum 7B) + /// + winstd::crypt_key create_des_key(_In_ HCRYPTPROV cp, _In_count_(size) const unsigned char *key, _In_ size_t size); +} + +#pragma once + +#include "../../EAPBase/include/EAP.h" + + +namespace eap +{ +#pragma pack(push) +#pragma pack(1) + + struct __declspec(novtable) challenge_mschapv2 : public sanitizing_blob_xf<16> + { + /// + /// Generate challenge random + /// + /// \param[in] cp Handle of the cryptographics provider + /// + void randomize(_In_ HCRYPTPROV cp); + }; + + + struct __declspec(novtable) challenge_hash : public sanitizing_blob_xf<8> + { + /// + /// Constructor + /// + challenge_hash(); + + /// + /// Constructs a challenge hash + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 8.2. ChallengeHash())](https://tools.ietf.org/html/rfc2759#section-8.2) + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] challenge_server Authenticator challenge + /// \param[in] challenge_client Peer challenge + /// \param[in] username Username + /// + challenge_hash( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username); + + /// + /// Copies a challenge hash + /// + /// \param[in] other Challenge hash to copy from + /// + challenge_hash(_In_ const sanitizing_blob_f<8> &other); + +#ifdef _DEBUG + /// + /// Moves the challenge hash + /// + /// \param[inout] other Challenge hash to move from + /// + challenge_hash(_Inout_ sanitizing_blob_zf<8> &&other); +#endif + }; + + + struct __declspec(novtable) nt_password_hash : public sanitizing_blob_xf<16> + { + /// + /// Constructor + /// + nt_password_hash(); + + /// + /// Constructs a NT-Password hash + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 8.3. NtPasswordHash())](https://tools.ietf.org/html/rfc2759#section-8.3) + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] password Password + /// + nt_password_hash( + _In_ HCRYPTPROV cp, + _In_z_ const wchar_t *password); + + /// + /// Constructs a hash of NT-Password hash + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 8.4. HashNtPasswordHash())](https://tools.ietf.org/html/rfc2759#section-8.4) + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] pwd_hash NT-Password hash + /// + nt_password_hash( + _In_ HCRYPTPROV cp, + _In_ const nt_password_hash &pwd_hash); + + /// + /// Copies a NT-Password hash + /// + /// \param[in] other NT-Password to copy from + /// + nt_password_hash(_In_ const sanitizing_blob_f<16> &other); + +#ifdef _DEBUG + /// + /// Moves the NT-Password hash + /// + /// \param[inout] other NT-Password hash to move from + /// + nt_password_hash(_Inout_ sanitizing_blob_zf<16> &&other); +#endif + }; + + + struct __declspec(novtable) nt_response : public sanitizing_blob_xf<24> + { + /// + /// Constructor + /// + nt_response(); + + /// + /// Constructs a NT-Response + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 8.1. GenerateNTResponse())](https://tools.ietf.org/html/rfc2759#section-8.1) + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] challenge_server Authenticator challenge + /// \param[in] challenge_client Peer challenge + /// \param[in] username Username + /// \param[in] password Password + /// + nt_response( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username, + _In_z_ const wchar_t *password); + + /// + /// Copies a NT-Response + /// + /// \param[in] other NT-Response to copy from + /// + nt_response(_In_ const sanitizing_blob_f<24> &other); + +#ifdef _DEBUG + /// + /// Moves the NT-Response + /// + /// \param[inout] other NT-Response to move from + /// + nt_response(_Inout_ sanitizing_blob_zf<24> &&other); +#endif + }; + + + struct __declspec(novtable) authenticator_response : public sanitizing_blob_xf<20> + { + /// + /// Constructor + /// + authenticator_response(); + + /// + /// Constructs an authenticator response + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 8.7. GenerateAuthenticatorResponse())](https://tools.ietf.org/html/rfc2759#section-8.7) + /// + /// \param[in] cp Handle of the cryptographics provider + /// \param[in] challenge_server Authenticator challenge + /// \param[in] challenge_client Peer challenge + /// \param[in] username Username + /// \param[in] password Password + /// \param[in] nt_resp NT-Response + /// + authenticator_response( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username, + _In_z_ const wchar_t *password, + _In_ const nt_response &nt_resp); + + /// + /// Copies an authenticator response + /// + /// \param[in] other Authenticator response to copy from + /// + authenticator_response(_In_ const sanitizing_blob_f<20> &other); + +#ifdef _DEBUG + /// + /// Moves the authenticator response + /// + /// \param[inout] other Authenticator response to move from + /// + authenticator_response(_Inout_ sanitizing_blob_zf<20> &&other); +#endif + }; + +#pragma pack(pop) +} diff --git a/lib/MSCHAPv2/include/Method.h b/lib/MSCHAPv2/include/Method.h index b8efad1..630c7ce 100644 --- a/lib/MSCHAPv2/include/Method.h +++ b/lib/MSCHAPv2/include/Method.h @@ -26,11 +26,11 @@ namespace eap class method_mschapv2; } - #pragma once #include "Config.h" #include "Credentials.h" +#include "MSCHAPv2.h" #include "../../EAPBase/include/Method.h" @@ -100,13 +100,52 @@ namespace eap /// @} + friend class method_ttls; // Setting of initial challenge derived from TLS PRF + protected: - credentials_mschapv2 &m_cred; ///< EAP-TLS user credentials + /// + /// Processes AVPs in a Diameter packet + /// + /// \param[in] pck Packet data + /// \param[in] size_pck \p pck size in bytes + /// + void process_packet(_In_bytecount_(size_pck) const void *pck, _In_ size_t size_pck); + + /// + /// Processes MS-CHAP2-Success AVP + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 5. Success Packet)](https://tools.ietf.org/html/rfc2759#section-5) + /// + /// \param[in] pck Packet data + /// \param[in] size_pck \p pck size in bytes + /// + void process_success(_In_ int argc, _In_count_(argc) const wchar_t *argv[]); + + /// + /// Processes MS-CHAP-Error AVP + /// + /// \sa [Microsoft PPP CHAP Extensions, Version 2 (Chapter 6. Failure Packet)](https://tools.ietf.org/html/rfc2759#section-6) + /// + /// \param[in] pck Packet data + /// \param[in] size_pck \p pck size in bytes + /// + void process_error(_In_ int argc, _In_count_(argc) const wchar_t *argv[]); + + protected: + credentials_mschapv2 &m_cred; ///< EAP-TLS user credentials + winstd::crypt_prov m_cp; ///< Cryptography provider for general services + + challenge_mschapv2 m_challenge_server; ///< MSCHAP server challenge + challenge_mschapv2 m_challenge_client; ///< MSCHAP client challenge + unsigned char m_ident; ///< Ident + nt_response m_nt_resp; ///< NT-Response + bool m_success; ///< Did we receive MS-CHAP2-Success? enum { - phase_unknown = -1, ///< Unknown phase - phase_init = 0, ///< Handshake initialize - phase_finished, ///< Connection shut down - } m_phase, m_phase_prev; ///< What phase is our communication at? + phase_unknown = -1, ///< Unknown phase + phase_init = 0, ///< Send client challenge + phase_challenge_server, ///< Verify server challenge + phase_finished, ///< Connection shut down + } m_phase, m_phase_prev; ///< What phase is our communication at? }; } diff --git a/lib/MSCHAPv2/src/MSCHAPv2.cpp b/lib/MSCHAPv2/src/MSCHAPv2.cpp new file mode 100644 index 0000000..7a9e7cd --- /dev/null +++ b/lib/MSCHAPv2/src/MSCHAPv2.cpp @@ -0,0 +1,336 @@ +/* + 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::create_des_key +////////////////////////////////////////////////////////////////////// + +crypt_key eap::create_des_key(_In_ HCRYPTPROV cp, _In_count_(size) const unsigned char *key, _In_ size_t size) +{ + // Prepare exported key BLOB. + struct key_blob_prefix { + PUBLICKEYSTRUC header; + DWORD size; + } static const s_prefix = { + { + PLAINTEXTKEYBLOB, + CUR_BLOB_VERSION, + 0, + CALG_DES, + }, + 8, + }; + sanitizing_blob key_blob; + key_blob.reserve(sizeof(key_blob_prefix) + 8); + key_blob.assign((const unsigned char*)&s_prefix, (const unsigned char*)(&s_prefix + 1)); + + // Inject parity bits. + unsigned char out = 0, parity = 1; + size_t i = 0, j = 7, bits = std::min(size * 8, 56); + for (; i < bits; i++) { + unsigned char bit = (key[i/8] & (1 << (7 - i%8))) ? 1 : 0; + parity ^= bit; + out |= bit << j; + if (--j == 0) { + out |= parity; + key_blob.push_back(out); + out = 0; parity = 1; j = 7; + } + } + for (; i < 56; i++) { + if (--j == 0) { + out |= parity; + key_blob.push_back(out); + out = 0; parity = 1; j = 7; + } + } + + // Import key. + crypt_key k; + if (!k.import(cp, key_blob.data(), (DWORD)key_blob.size(), NULL, 0)) + throw winstd::win_runtime_error(__FUNCTION__ " Error importing key 1/3."); + return k; +} + + + +////////////////////////////////////////////////////////////////////// +// eap::challenge_mschapv2 +////////////////////////////////////////////////////////////////////// + +void eap::challenge_mschapv2::randomize(_In_ HCRYPTPROV cp) +{ + if (!CryptGenRandom(cp, sizeof(data), data)) + throw win_runtime_error(__FUNCTION__ " Error creating randomness."); +} + + +////////////////////////////////////////////////////////////////////// +// eap::challenge_hash +////////////////////////////////////////////////////////////////////// + +eap::challenge_hash::challenge_hash() +{ +} + + +eap::challenge_hash::challenge_hash( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username) +{ + const char *domain = strchr(username, '@'); + size_t len_username = domain ? domain - username : strlen(username); + + crypt_hash hash; + if (!hash.create(cp, CALG_SHA)) + throw win_runtime_error(__FUNCTION__ " Creating SHA hash failed."); + if (!CryptHashData(hash, (const BYTE*)&challenge_client, (DWORD)sizeof(challenge_client), 0) || + !CryptHashData(hash, (const BYTE*)&challenge_server, (DWORD)sizeof(challenge_server), 0) || + !CryptHashData(hash, (const BYTE*)username , (DWORD)len_username , 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + unsigned char hash_val[20]; + DWORD size_hash_val = sizeof(hash_val); + if (!CryptGetHashParam(hash, HP_HASHVAL, hash_val, &size_hash_val, 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + memcpy(data, hash_val, sizeof(data)); + SecureZeroMemory(hash_val, size_hash_val); +} + + +eap::challenge_hash::challenge_hash(_In_ const sanitizing_blob_f<8> &other) : + sanitizing_blob_xf<8>(other) +{ +} + + +#ifdef _DEBUG + +eap::challenge_hash::challenge_hash(_Inout_ sanitizing_blob_zf<8> &&other) : + sanitizing_blob_xf<8>(std::move(other)) +{ +} + +#endif + + +////////////////////////////////////////////////////////////////////// +// eap::nt_password_hash +////////////////////////////////////////////////////////////////////// + +eap::nt_password_hash::nt_password_hash() +{ +} + + +eap::nt_password_hash::nt_password_hash( + _In_ HCRYPTPROV cp, + _In_z_ const wchar_t *password) +{ + crypt_hash hash; + if (!hash.create(cp, CALG_MD4)) + throw win_runtime_error(__FUNCTION__ " Creating MD4 hash failed."); + if (!CryptHashData(hash, (const BYTE*)password, (DWORD)(wcslen(password) * sizeof(wchar_t)), 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + DWORD size_data = sizeof(data); + if (!CryptGetHashParam(hash, HP_HASHVAL, data, &size_data, 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); +} + + +eap::nt_password_hash::nt_password_hash( + _In_ HCRYPTPROV cp, + _In_ const nt_password_hash &pwd_hash) +{ + crypt_hash hash; + if (!hash.create(cp, CALG_MD4)) + throw win_runtime_error(__FUNCTION__ " Creating MD4 hash failed."); + if (!CryptHashData(hash, (const BYTE*)&pwd_hash, (DWORD)sizeof(pwd_hash), 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + DWORD size_data = sizeof(data); + if (!CryptGetHashParam(hash, HP_HASHVAL, data, &size_data, 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); +} + + +eap::nt_password_hash::nt_password_hash(_In_ const sanitizing_blob_f<16> &other) : + sanitizing_blob_xf<16>(other) +{ +} + + +#ifdef _DEBUG + +eap::nt_password_hash::nt_password_hash(_Inout_ sanitizing_blob_zf<16> &&other) : + sanitizing_blob_xf<16>(std::move(other)) +{ +} + +#endif + + +////////////////////////////////////////////////////////////////////// +// eap::nt_response +////////////////////////////////////////////////////////////////////// + +eap::nt_response::nt_response() +{ +} + + +eap::nt_response::nt_response( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username, + _In_z_ const wchar_t *password) +{ + challenge_hash challenge(cp, challenge_server, challenge_client, username); + nt_password_hash hash_pwd(cp, password); + + // Prepare exported key BLOB. + crypt_key key; + DWORD size_data_enc; + static const DWORD mode_ecb = CRYPT_MODE_ECB; + + // DesEncrypt(Challenge, 1st 7-octets of ZPasswordHash, giving 1st 8-octets of Response) + key = create_des_key(cp, (const unsigned char*)&hash_pwd, 7); + if (!CryptSetKeyParam(key, KP_MODE, (const BYTE*)&mode_ecb, 0)) + throw win_runtime_error(__FUNCTION__ " Error setting ECB mode."); + memcpy(data, &challenge, 8); + size_data_enc = 8; + if (!CryptEncrypt(key, NULL, FALSE, 0, data, &size_data_enc, 8)) + throw win_runtime_error(__FUNCTION__ " Error encrypting message 1/3."); + + // DesEncrypt(Challenge, 2nd 7-octets of ZPasswordHash, giving 2nd 8-octets of Response) + key = create_des_key(cp, (const unsigned char*)&hash_pwd + 7, 7); + if (!CryptSetKeyParam(key, KP_MODE, (const BYTE*)&mode_ecb, 0)) + throw win_runtime_error(__FUNCTION__ " Error setting ECB mode."); + memcpy(data + 8, &challenge, 8); + size_data_enc = 8; + if (!CryptEncrypt(key, NULL, FALSE, 0, data + 8, &size_data_enc, 8)) + throw win_runtime_error(__FUNCTION__ " Error encrypting message 2/3."); + + // DesEncrypt(Challenge, 2nd 7-octets of ZPasswordHash, giving 2nd 8-octets of Response) + key = create_des_key(cp, (const unsigned char*)&hash_pwd + 14, 2); + if (!CryptSetKeyParam(key, KP_MODE, (const BYTE*)&mode_ecb, 0)) + throw win_runtime_error(__FUNCTION__ " Error setting ECB mode."); + memcpy(data + 16, &challenge, 8); + size_data_enc = 8; + if (!CryptEncrypt(key, NULL, FALSE, 0, data + 16, &size_data_enc, 8)) + throw win_runtime_error(__FUNCTION__ " Error encrypting message 3/3."); +} + + +eap::nt_response::nt_response(_In_ const sanitizing_blob_f<24> &other) : + sanitizing_blob_xf<24>(other) +{ +} + + +#ifdef _DEBUG + +eap::nt_response::nt_response(_Inout_ sanitizing_blob_zf<24> &&other) : + sanitizing_blob_xf<24>(std::move(other)) +{ +} + +#endif + + +////////////////////////////////////////////////////////////////////// +// eap::authenticator_response +////////////////////////////////////////////////////////////////////// + +eap::authenticator_response::authenticator_response() +{ +} + + +eap::authenticator_response::authenticator_response( + _In_ HCRYPTPROV cp, + _In_ const challenge_mschapv2 &challenge_server, + _In_ const challenge_mschapv2 &challenge_client, + _In_z_ const char *username, + _In_z_ const wchar_t *password, + _In_ const nt_response &nt_resp) +{ + static const unsigned char s_magic1[39] = { + 0x4d, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74 + }; + nt_password_hash hash_hash_pwd(cp, nt_password_hash(cp, password)); + + crypt_hash hash; + if (!hash.create(cp, CALG_SHA)) + throw win_runtime_error(__FUNCTION__ " Creating SHA hash failed."); + if (!CryptHashData(hash, (const BYTE*)&hash_hash_pwd, (DWORD)sizeof(hash_hash_pwd), 0) || + !CryptHashData(hash, (const BYTE*)&nt_resp , (DWORD)sizeof(nt_resp ), 0) || + !CryptHashData(hash, s_magic1 , (DWORD)sizeof(s_magic1 ), 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + unsigned char hash_val[20]; + DWORD size_hash_val = sizeof(hash_val); + if (!CryptGetHashParam(hash, HP_HASHVAL, hash_val, &size_hash_val, 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + + static const unsigned char s_magic2[41] = { + 0x50, 0x61, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6b, + 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x20, 0x6d, 0x6f, + 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x6f, 0x6e, + 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e + }; + challenge_hash challenge(cp, challenge_server, challenge_client, username); + + if (!hash.create(cp, CALG_SHA)) + throw win_runtime_error(__FUNCTION__ " Creating SHA hash failed."); + if (!CryptHashData(hash, hash_val , size_hash_val , 0) || + !CryptHashData(hash, (const BYTE*)&challenge, (DWORD)sizeof(challenge), 0) || + !CryptHashData(hash, s_magic2 , (DWORD)sizeof(s_magic2 ), 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); + size_hash_val = sizeof(data); + if (!CryptGetHashParam(hash, HP_HASHVAL, data, &size_hash_val, 0)) + throw win_runtime_error(__FUNCTION__ " Error hashing data."); +} + + +eap::authenticator_response::authenticator_response(_In_ const sanitizing_blob_f<20> &other) : + sanitizing_blob_xf<20>(other) +{ +} + + +#ifdef _DEBUG + +eap::authenticator_response::authenticator_response(_Inout_ sanitizing_blob_zf<20> &&other) : + sanitizing_blob_xf<20>(std::move(other)) +{ +} + +#endif diff --git a/lib/MSCHAPv2/src/Method.cpp b/lib/MSCHAPv2/src/Method.cpp index 31ebd4b..657b70f 100644 --- a/lib/MSCHAPv2/src/Method.cpp +++ b/lib/MSCHAPv2/src/Method.cpp @@ -30,6 +30,8 @@ using namespace winstd; eap::method_mschapv2::method_mschapv2(_In_ module &module, _In_ config_method_mschapv2 &cfg, _In_ credentials_mschapv2 &cred) : m_cred(cred), + m_ident(0), + m_success(false), m_phase(phase_unknown), m_phase_prev(phase_unknown), method_noneap(module, cfg, cred) @@ -38,10 +40,16 @@ eap::method_mschapv2::method_mschapv2(_In_ module &module, _In_ config_method_ms eap::method_mschapv2::method_mschapv2(_Inout_ method_mschapv2 &&other) : - m_cred ( other.m_cred ), - m_phase (std::move(other.m_phase )), - m_phase_prev (std::move(other.m_phase_prev)), - method_noneap(std::move(other )) + m_cred ( other.m_cred ), + m_cp (std::move(other.m_cp )), + m_challenge_server(std::move(other.m_challenge_server)), + m_challenge_client(std::move(other.m_challenge_client)), + m_ident (std::move(other.m_ident )), + m_nt_resp (std::move(other.m_nt_resp )), + m_success (std::move(other.m_success )), + m_phase (std::move(other.m_phase )), + m_phase_prev (std::move(other.m_phase_prev )), + method_noneap (std::move(other )) { } @@ -50,9 +58,15 @@ eap::method_mschapv2& eap::method_mschapv2::operator=(_Inout_ method_mschapv2 && { if (this != std::addressof(other)) { assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only! - (method_noneap&)*this = std::move(other ); - m_phase = std::move(other.m_phase ); - m_phase_prev = std::move(other.m_phase_prev); + (method_noneap&)*this = std::move(other ); + m_cp = std::move(other.m_cp ); + m_challenge_server = std::move(other.m_challenge_server); + m_challenge_client = std::move(other.m_challenge_client); + m_ident = std::move(other.m_ident ); + m_nt_resp = std::move(other.m_nt_resp ); + m_success = std::move(other.m_success ); + m_phase = std::move(other.m_phase ); + m_phase_prev = std::move(other.m_phase_prev ); } return *this; @@ -67,6 +81,10 @@ void eap::method_mschapv2::begin_session( { method_noneap::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize); + // Create cryptographics provider for support needs (client challenge ...). + if (!m_cp.create(NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) + throw win_runtime_error(__FUNCTION__ " Error creating cryptographics provider."); + m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)eap_type_legacy_mschapv2), event_data::blank); m_phase = phase_init; } @@ -82,23 +100,36 @@ void eap::method_mschapv2::process_request_packet( m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)eap_type_legacy_mschapv2), event_data((unsigned int)dwReceivedPacketSize), event_data::blank); + process_packet(pReceivedPacket, dwReceivedPacketSize); + m_phase_prev = m_phase; switch (m_phase) { case phase_init: { - // Convert username and password to UTF-8. - sanitizing_string identity_utf8, password_utf8; + // Convert username to UTF-8. + sanitizing_string identity_utf8; WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); - WideCharToMultiByte(CP_UTF8, 0, m_cred.m_password.c_str(), (int)m_cred.m_password.length(), password_utf8, NULL, NULL); - // PAP passwords must be padded to 16B boundary according to RFC 5281. Will not add random extra padding here, as length obfuscation should be done by outer transport layers. - size_t padding_password_ex = (16 - password_utf8.length()) % 16; - password_utf8.append(padding_password_ex, 0); + // Prepare MS-CHAP2-Response + sanitizing_blob response; + response.push_back(m_ident); // Ident + response.push_back(0); // Flags + m_challenge_client.randomize(m_cp); + response.insert(response.end(), (unsigned char*)&m_challenge_client, (unsigned char*)(&m_challenge_client + 1)); // Peer-Challenge + response.insert(response.end(), 8, 0); // Reserved + m_nt_resp = nt_response(m_cp, m_challenge_server, m_challenge_client, identity_utf8.c_str(), m_cred.m_password.c_str()); + response.insert(response.end(), (unsigned char*)&m_nt_resp, (unsigned char*)(&m_nt_resp + 1)); // NT-Response - // Diameter AVP (User-Name=0x00000001, User-Password=0x00000002) - append_avp(0x00000001, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size()); - append_avp(0x00000002, diameter_avp_flag_mandatory, password_utf8.data(), (unsigned int)password_utf8.size()); + // Diameter AVP (User-Name=1, MS-CHAP-Challenge=11/311, MS-CHAP2-Response=25/311) + append_avp( 1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size() ); + append_avp(11, 311, diameter_avp_flag_mandatory, (unsigned char*)&m_challenge_server , (unsigned int)sizeof(m_challenge_server)); + append_avp(25, 311, diameter_avp_flag_mandatory, response.data() , (unsigned int)response.size() ); - m_phase = phase_finished; + m_phase = phase_challenge_server; + break; + } + + case phase_challenge_server: { + // We do not need to do anything. break; } @@ -151,3 +182,90 @@ void eap::method_mschapv2::get_result( // Always ask EAP host to save the connection data. ppResult->fSaveConnectionData = TRUE; } + + +void eap::method_mschapv2::process_packet(_In_bytecount_(size_pck) const void *_pck, _In_ size_t size_pck) +{ + sanitizing_blob data; + wstring msg_w; + + for (const unsigned char *pck = (const unsigned char*)_pck, *pck_end = pck + size_pck; pck < pck_end; ) { + if (pck + sizeof(diameter_avp_header) > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message header."); + const diameter_avp_header *hdr = (const diameter_avp_header*)pck; + unsigned int code = ntohl(*(unsigned int*)hdr->code); + unsigned int vendor; + const unsigned char *msg; + if (hdr->flags & diameter_avp_flag_vendor) { + if (pck + sizeof(diameter_avp_header_ven) > pck_end) + throw win_runtime_error(EAP_E_EAPHOST_METHOD_INVALID_PACKET, __FUNCTION__ " Incomplete message header."); + const diameter_avp_header_ven *hdr_ven = (const diameter_avp_header_ven*)pck; + vendor = ntohl(*(unsigned int*)hdr_ven->vendor); + msg = (const unsigned char*)(hdr_ven + 1); + } else { + vendor = 0; + msg = (const unsigned char*)(hdr + 1); + } + const unsigned char *msg_end = pck + 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 (code == 26 && vendor == 311) { + // MS-CHAP2-Success + MultiByteToWideChar(CP_UTF8, 0, (const char*)msg, (int)(msg_end - msg), msg_w); + int argc; + unique_ptr > argv(CommandLineToArgvW(msg_w.c_str(), &argc)); + if (!argv) argc = 0; + process_success(argc, (const wchar_t**)argv.get()); + } else if (code == 2 && vendor == 311) { + // MS-CHAP2-Error + MultiByteToWideChar(CP_UTF8, 0, (const char*)msg, (int)(msg_end - msg), msg_w); + int argc; + unique_ptr > argv(CommandLineToArgvW(msg_w.c_str(), &argc)); + if (!argv) argc = 0; + process_error(argc, (const wchar_t**)argv.get()); + } else if (hdr->flags & diameter_avp_flag_mandatory) + throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Server sent mandatory Diameter AVP we do not support (code: %u, vendor: %u).", code, vendor).c_str()); + + pck = msg_end; + } +} + + +void eap::method_mschapv2::process_success(_In_ int argc, _In_count_(argc) const wchar_t *argv[]) +{ + m_success = false; + + for (int i = 0; i < argc; i++) { + if ((argv[i][0] == L'S' || argv[i][0] == L's') && argv[i][1] == L'=') { + // "S=" + hex_dec dec; + sanitizing_blob resp; + bool is_last; + dec.decode(resp, is_last, argv[i] + 2, (size_t)-1); + + // Calculate expected authenticator response. + sanitizing_string identity_utf8; + WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL); + authenticator_response resp_exp(m_cp, m_challenge_server, m_challenge_client, identity_utf8.c_str(), m_cred.m_password.c_str(), m_nt_resp); + + // Compare against provided authemticator response. + if (resp.size() != sizeof(resp_exp) || memcpy(resp.data(), &resp_exp, sizeof(resp_exp)) != 0) + throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string failed."); + + m_success = true; + } + } + + if (!m_success) + throw invalid_argument(__FUNCTION__ " MS-CHAP2-Success authentication response string not found."); + + //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); +} + + +void eap::method_mschapv2::process_error(_In_ int argc, _In_count_(argc) const wchar_t *argv[]) +{ + UNREFERENCED_PARAMETER(argc); + UNREFERENCED_PARAMETER(argv); +} diff --git a/lib/MSCHAPv2/src/StdAfx.h b/lib/MSCHAPv2/src/StdAfx.h index cc1d372..8e07868 100644 --- a/lib/MSCHAPv2/src/StdAfx.h +++ b/lib/MSCHAPv2/src/StdAfx.h @@ -23,6 +23,9 @@ #include "../include/Config.h" #include "../include/Credentials.h" #include "../include/Method.h" +#include "../include/MSCHAPv2.h" + +#include #include #include // include after Windows.h diff --git a/lib/TTLS/src/Method.cpp b/lib/TTLS/src/Method.cpp index 0ab2e19..5f44d00 100644 --- a/lib/TTLS/src/Method.cpp +++ b/lib/TTLS/src/Method.cpp @@ -245,9 +245,15 @@ void eap::method_ttls::process_application_data(_In_bytecount_(size_msg) const v } #endif + method_mschapv2 *inner_mschapv2 = dynamic_cast(m_inner.get()); + if (inner_mschapv2) { + sanitizing_blob keying(derive_challenge(sizeof(challenge_mschapv2) + 1)); + memcpy(&inner_mschapv2->m_challenge_server, keying.data(), sizeof(challenge_mschapv2)); + inner_mschapv2->m_ident = keying[sizeof(challenge_mschapv2) + 0]; + } + EapPeerMethodOutput eap_output = {}; m_inner->process_request_packet(msg, (DWORD)size_msg, &eap_output); - switch (eap_output.action) { case EapPeerMethodResponseActionSend: { // Retrieve inner packet and send it. diff --git a/lib/TTLS/src/StdAfx.h b/lib/TTLS/src/StdAfx.h index e030902..4dac674 100644 --- a/lib/TTLS/src/StdAfx.h +++ b/lib/TTLS/src/StdAfx.h @@ -33,6 +33,7 @@ #include "../../MSCHAPv2/include/Config.h" #include "../../MSCHAPv2/include/Credentials.h" #include "../../MSCHAPv2/include/Method.h" +#include "../../MSCHAPv2/include/MSCHAPv2.h" #include "../../EAPBase/include/EAPXML.h"