mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 15:27:21 +00:00
[client/idd] network: add password authentication
This commit is contained in:
@@ -91,6 +91,7 @@
|
||||
<ClCompile Include="transport\network\CNetworkServer.cpp" />
|
||||
<ClCompile Include="transport\network\CNetworkSession.cpp" />
|
||||
<ClCompile Include="transport\network\CNetworkTransport.cpp" />
|
||||
<ClCompile Include="transport\network\NetworkAuthCrypto.cpp" />
|
||||
<ClCompile Include="transport\network\NetworkSettings.cpp" />
|
||||
<ClCompile Include="config\CSettings.cpp" />
|
||||
<ClCompile Include="platform\CPlatformInfo.cpp" />
|
||||
@@ -175,6 +176,7 @@
|
||||
<ClInclude Include="transport\network\CNetworkServer.h" />
|
||||
<ClInclude Include="transport\network\CNetworkSession.h" />
|
||||
<ClInclude Include="transport\network\CNetworkTransport.h" />
|
||||
<ClInclude Include="transport\network\NetworkAuthCrypto.h" />
|
||||
<ClInclude Include="transport\network\NetworkSettings.h" />
|
||||
<ClInclude Include="config\CSettings.h" />
|
||||
<ClInclude Include="platform\CPlatformInfo.h" />
|
||||
|
||||
@@ -295,6 +295,9 @@
|
||||
<ClInclude Include="transport\network\CNetworkTransport.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\network\NetworkAuthCrypto.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\network\NetworkSettings.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
@@ -483,6 +486,9 @@
|
||||
<ClCompile Include="transport\network\CNetworkTransport.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\network\NetworkAuthCrypto.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\network\NetworkSettings.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
#include "transport/network/CNetworkSession.h"
|
||||
#include "transport/network/CNetworkTransport.h"
|
||||
#include "transport/network/NetworkAuthCrypto.h"
|
||||
#include "transport/network/NetworkSettings.h"
|
||||
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
#include <LGProtocol/NetworkAuth.h>
|
||||
#include <LGProtocol/NetworkIdentity.h>
|
||||
#include <LGProtocol/NetworkPacket.h>
|
||||
#include <LGProtocol/NetworkRoute.h>
|
||||
@@ -34,7 +36,6 @@
|
||||
#include <LGProtocol/NetworkStream.h>
|
||||
|
||||
#include <msquic.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits.h>
|
||||
@@ -47,44 +48,96 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
static constexpr uint64_t NETWORK_HANDSHAKE_TIMEOUT_MS = 5000;
|
||||
static constexpr uint64_t NETWORK_IDLE_TIMEOUT_MS = 30000;
|
||||
static constexpr uint32_t NETWORK_KEEPALIVE_INTERVAL_MS = 1000;
|
||||
static constexpr uint32_t NETWORK_INITIAL_RTT_MS = 1;
|
||||
static constexpr uint16_t NETWORK_PEER_BIDI_STREAMS = 16;
|
||||
static constexpr uint16_t NETWORK_PEER_UNIDI_STREAMS = 16;
|
||||
static constexpr size_t NETWORK_MAX_CONTROL_PAYLOAD = 1024 * 1024;
|
||||
static constexpr size_t NETWORK_MAX_CONTROL_BUFFER =
|
||||
static constexpr uint64_t NETWORK_HANDSHAKE_TIMEOUT_MS = 5000;
|
||||
static constexpr uint64_t NETWORK_IDLE_TIMEOUT_MS = 30000;
|
||||
static constexpr uint32_t NETWORK_KEEPALIVE_INTERVAL_MS = 1000;
|
||||
static constexpr uint32_t NETWORK_INITIAL_RTT_MS = 1;
|
||||
static constexpr uint16_t NETWORK_PEER_BIDI_STREAMS = 16;
|
||||
static constexpr uint16_t NETWORK_PEER_UNIDI_STREAMS = 16;
|
||||
static constexpr size_t NETWORK_MAX_CONTROL_PAYLOAD = 1024 * 1024;
|
||||
static constexpr size_t NETWORK_MAX_CONTROL_BUFFER =
|
||||
LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD;
|
||||
static constexpr uint32_t NETWORK_MAX_DATAGRAM_PAYLOAD = 1136;
|
||||
static constexpr uint32_t NETWORK_INPUT_LEASE_MS = 500;
|
||||
static constexpr uint8_t NETWORK_KEYBOARD_ROLLOVER = 0x01;
|
||||
static constexpr uint16_t NETWORK_KEYBOARD_USAGE_PAGE = 0x07;
|
||||
static constexpr size_t NETWORK_KEYBOARD_KEY_COUNT = 6;
|
||||
static constexpr size_t NETWORK_SERVICE_COUNT =
|
||||
static constexpr uint32_t NETWORK_MAX_DATAGRAM_PAYLOAD = 1136;
|
||||
static constexpr uint32_t NETWORK_INPUT_LEASE_MS = 500;
|
||||
static constexpr uint8_t NETWORK_KEYBOARD_ROLLOVER = 0x01;
|
||||
static constexpr uint16_t NETWORK_KEYBOARD_USAGE_PAGE = 0x07;
|
||||
static constexpr size_t NETWORK_KEYBOARD_KEY_COUNT = 6;
|
||||
static constexpr size_t NETWORK_SERVICE_COUNT =
|
||||
LG_NET_SERVICE_USB + 1U;
|
||||
static constexpr uint64_t NETWORK_SEQUENCE_COMPONENT = UINT64_C(1);
|
||||
static constexpr uint8_t NETWORK_DATAGRAM_WINDOW = 64;
|
||||
static constexpr uint32_t NETWORK_KEYBOARD_LED_MASK =
|
||||
static constexpr uint64_t NETWORK_SEQUENCE_COMPONENT = UINT64_C(1);
|
||||
static constexpr uint8_t NETWORK_DATAGRAM_WINDOW = 64;
|
||||
static constexpr uint32_t NETWORK_PASSWORD_ROUNDS = 100000;
|
||||
static constexpr uint16_t NETWORK_PASSWORD_SALT_SIZE = 32;
|
||||
static constexpr uint16_t NETWORK_PASSWORD_CHALLENGE_SIZE = 32;
|
||||
static constexpr uint16_t NETWORK_PASSWORD_NONCE_SIZE = 32;
|
||||
static constexpr uint32_t NETWORK_AUTH_RETRY_AFTER_MS = 1000;
|
||||
static constexpr uint32_t NETWORK_KEYBOARD_LED_MASK =
|
||||
LG_NET_KEYBOARD_LED_NUM_LOCK |
|
||||
LG_NET_KEYBOARD_LED_CAPS_LOCK |
|
||||
LG_NET_KEYBOARD_LED_SCROLL_LOCK |
|
||||
LG_NET_KEYBOARD_LED_COMPOSE |
|
||||
LG_NET_KEYBOARD_LED_KANA;
|
||||
|
||||
static constexpr QUIC_UINT62 NETWORK_ERROR_PROTOCOL = 1;
|
||||
static constexpr QUIC_UINT62 NETWORK_ERROR_SERVER_STOPPING = 2;
|
||||
static constexpr QUIC_UINT62 NETWORK_ERROR_PROTOCOL = 1;
|
||||
static constexpr QUIC_UINT62 NETWORK_ERROR_SERVER_STOPPING = 2;
|
||||
static constexpr QUIC_UINT62 NETWORK_ERROR_HANDSHAKE_TIMEOUT = 3;
|
||||
|
||||
static const wchar_t NETWORK_REGISTRY_KEY[] =
|
||||
L"Software\\LookingGlass\\IDD";
|
||||
|
||||
bool FillRandom(void * data, size_t size)
|
||||
static_assert(NetworkAuthCrypto::SHA256_SIZE ==
|
||||
LG_NET_PASSWORD_SHA256_LENGTH, "password proof size mismatch");
|
||||
|
||||
bool RandomNonzero(void * data, size_t size)
|
||||
{
|
||||
if (!data || size > ULONG_MAX)
|
||||
if (!data || !size)
|
||||
return false;
|
||||
|
||||
return BCryptGenRandom(nullptr, static_cast<PUCHAR>(data),
|
||||
static_cast<ULONG>(size), BCRYPT_USE_SYSTEM_PREFERRED_RNG) >= 0;
|
||||
for (unsigned attempt = 0; attempt < 2; ++attempt)
|
||||
{
|
||||
if (!NetworkAuthCrypto::RandomBytes(data, size))
|
||||
return false;
|
||||
|
||||
const auto * bytes = static_cast<const uint8_t *>(data);
|
||||
for (size_t index = 0; index < size; ++index)
|
||||
if (bytes[index])
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkAuthCrypto::SecureZero(data, size);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PasswordUTF8(const std::wstring& source,
|
||||
std::vector<uint8_t>& password)
|
||||
{
|
||||
if (source.empty() || source.size() > INT_MAX)
|
||||
return false;
|
||||
|
||||
const int length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
source.data(), static_cast<int>(source.size()), nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length <= 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
password.resize(static_cast<size_t>(length));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
source.data(), static_cast<int>(source.size()),
|
||||
reinterpret_cast<char *>(password.data()), length,
|
||||
nullptr, nullptr) == length)
|
||||
return true;
|
||||
|
||||
NetworkAuthCrypto::SecureZero(password.data(), password.size());
|
||||
password.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MakeAddress(const std::wstring& source, uint16_t port,
|
||||
@@ -162,6 +215,10 @@ struct CNetworkServer::Impl
|
||||
{
|
||||
WAITING_HELLO,
|
||||
WAITING_CAPABILITIES,
|
||||
WAITING_AUTH_RESPONSE,
|
||||
AUTHENTICATING,
|
||||
PUBLISHING_CAPABILITIES,
|
||||
AUTH_FAILED,
|
||||
READY,
|
||||
};
|
||||
|
||||
@@ -169,6 +226,14 @@ struct CNetworkServer::Impl
|
||||
{
|
||||
explicit Connection(Impl& owner) : owner(owner) {}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(clientNonce, sizeof(clientNonce));
|
||||
NetworkAuthCrypto::SecureZero(serverNonce, sizeof(serverNonce));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
&passwordChallenge, sizeof(passwordChallenge));
|
||||
}
|
||||
|
||||
Impl& owner;
|
||||
CSRWLock lock;
|
||||
HQUIC handle = nullptr;
|
||||
@@ -180,12 +245,14 @@ struct CNetworkServer::Impl
|
||||
uint64_t datagramSendSequence = 0;
|
||||
uint64_t receiveSequence = 0;
|
||||
uint64_t handshakeRequestID = 0;
|
||||
uint64_t handshakeDeadline = 0;
|
||||
uint32_t maxPayload =
|
||||
static_cast<uint32_t>(NETWORK_MAX_CONTROL_PAYLOAD);
|
||||
uint32_t maxDatagramPayload = 0;
|
||||
uint16_t maxDatagramSendLength = 0;
|
||||
uint32_t expectedCapabilities = 0;
|
||||
uint32_t receivedCapabilities = 0;
|
||||
uint32_t selectedCapabilityCount = 0;
|
||||
uint16_t offeredServices = 0;
|
||||
uint16_t selectedServices = 0;
|
||||
uint16_t reliableServices = 0;
|
||||
@@ -207,8 +274,12 @@ struct CNetworkServer::Impl
|
||||
uint8_t keyboardModifiers = 0;
|
||||
bool inputLEDStateKnown = false;
|
||||
bool keyboardKeys[256] = {};
|
||||
|
||||
uint8_t clientNonce[LG_NET_HELLO_NONCE_LENGTH] = {};
|
||||
uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH] = {};
|
||||
|
||||
LGNetPasswordChallenge passwordChallenge = {};
|
||||
|
||||
std::shared_ptr<CNetworkSession> session;
|
||||
LGNetSequenceTracker datagramReceive = {};
|
||||
LGNetStreamParser receiveParser = {};
|
||||
@@ -239,6 +310,12 @@ struct CNetworkServer::Impl
|
||||
~Impl()
|
||||
{
|
||||
Stop();
|
||||
if (!settings.password.empty())
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(&settings.password[0],
|
||||
settings.password.size() * sizeof(settings.password[0]));
|
||||
settings.password.clear();
|
||||
}
|
||||
if (drainedEvent)
|
||||
CloseHandle(drainedEvent);
|
||||
}
|
||||
@@ -352,7 +429,10 @@ struct CNetworkServer::Impl
|
||||
const uint16_t serviceMask = UINT16_C(1) << service;
|
||||
if (connection.closing || !connection.controlStream ||
|
||||
(service != LG_NET_SERVICE_CORE &&
|
||||
(!(connection.selectedServices & serviceMask) ||
|
||||
((connection.handshake != HandshakeState::READY &&
|
||||
connection.handshake !=
|
||||
HandshakeState::PUBLISHING_CAPABILITIES) ||
|
||||
!(connection.selectedServices & serviceMask) ||
|
||||
!(connection.reliableServices & serviceMask))))
|
||||
return false;
|
||||
const uint32_t maxPayload = service == LG_NET_SERVICE_CORE ?
|
||||
@@ -502,13 +582,14 @@ struct CNetworkServer::Impl
|
||||
hello.protocolMin > LG_NET_PROTOCOL_VERSION_CURRENT ||
|
||||
hello.protocolMax < LG_NET_PROTOCOL_VERSION_CURRENT ||
|
||||
(hello.authModes & ~LG_NET_AUTH_MODES_KNOWN) != 0 ||
|
||||
!(hello.authModes & LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE)))
|
||||
!(hello.authModes & LG_NET_AUTH_MODE_FLAG(settings.auth)))
|
||||
return false;
|
||||
|
||||
uint64_t sessionEpoch = 0;
|
||||
uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH];
|
||||
if (!FillRandom(&sessionEpoch, sizeof(sessionEpoch)) ||
|
||||
!FillRandom(serverNonce, sizeof(serverNonce)))
|
||||
if (!NetworkAuthCrypto::RandomBytes(
|
||||
&sessionEpoch, sizeof(sessionEpoch)) ||
|
||||
!RandomNonzero(serverNonce, sizeof(serverNonce)))
|
||||
return false;
|
||||
if (!sessionEpoch)
|
||||
sessionEpoch = GetTickCount64() | 1;
|
||||
@@ -518,25 +599,17 @@ struct CNetworkServer::Impl
|
||||
NETWORK_SEQUENCE_COMPONENT, NETWORK_DATAGRAM_WINDOW, false))
|
||||
return false;
|
||||
|
||||
std::shared_ptr<CNetworkSession> session = transport.CreateSession(
|
||||
connection.client, connection.generation);
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
if (connection.closing ||
|
||||
connection.handshake != HandshakeState::WAITING_HELLO)
|
||||
{
|
||||
transport.RemoveSession(connection.client, connection.generation);
|
||||
return false;
|
||||
}
|
||||
connection.session = std::move(session);
|
||||
connection.sessionEpoch = sessionEpoch;
|
||||
connection.datagramReceive = datagramReceive;
|
||||
connection.handshakeRequestID = packet.envelope.requestID;
|
||||
connection.expectedCapabilities = hello.capabilityCount;
|
||||
connection.receivedCapabilities = 0;
|
||||
connection.selectedCapabilityCount = 0;
|
||||
connection.maxPayload = hello.maxPayloadLength <
|
||||
NETWORK_MAX_CONTROL_PAYLOAD ? hello.maxPayloadLength :
|
||||
static_cast<uint32_t>(NETWORK_MAX_CONTROL_PAYLOAD);
|
||||
@@ -626,22 +699,169 @@ struct CNetworkServer::Impl
|
||||
connection.maxDatagramPayload >= LG_NET_INPUT_ABSOLUTE_WIRE_SIZE)
|
||||
{
|
||||
selected.flags |= LG_NET_CAPABILITY_DATAGRAM;
|
||||
selected.maxDatagramPayload = offered.maxDatagramPayload <
|
||||
selected.maxDatagramPayload = offered.maxDatagramPayload <
|
||||
connection.maxDatagramPayload ? offered.maxDatagramPayload :
|
||||
connection.maxDatagramPayload;
|
||||
}
|
||||
return lgNetCapabilityValid(&selected);
|
||||
}
|
||||
|
||||
bool FinishCapabilities(Connection& connection,
|
||||
const LGNetPacketView& packet)
|
||||
bool SendHelloAcknowledgement(Connection& connection)
|
||||
{
|
||||
LGNetCapability selected[NETWORK_SERVICE_COUNT] = {};
|
||||
uint32_t selectedCount = 0;
|
||||
uint64_t sessionEpoch;
|
||||
uint32_t maxPayload;
|
||||
uint32_t maxDatagramPayload;
|
||||
uint32_t capabilityCount;
|
||||
uint64_t requestID;
|
||||
uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH];
|
||||
{
|
||||
CSRWSharedLock guard(connection.lock);
|
||||
if (connection.closing)
|
||||
return false;
|
||||
sessionEpoch = connection.sessionEpoch;
|
||||
maxPayload = connection.maxPayload;
|
||||
maxDatagramPayload = connection.maxDatagramPayload;
|
||||
capabilityCount = connection.selectedCapabilityCount;
|
||||
requestID = connection.handshakeRequestID;
|
||||
memcpy(serverNonce, connection.serverNonce, sizeof(serverNonce));
|
||||
}
|
||||
|
||||
LGNetHelloAck acknowledgement;
|
||||
lgNetHelloAckInit(&acknowledgement, sessionEpoch);
|
||||
acknowledgement.authMode = settings.auth;
|
||||
acknowledgement.maxPayloadLength = maxPayload;
|
||||
acknowledgement.maxDatagramPayload = maxDatagramPayload;
|
||||
acknowledgement.capabilityCount = capabilityCount;
|
||||
memcpy(acknowledgement.nonce, serverNonce, sizeof(serverNonce));
|
||||
|
||||
uint8_t payload[LG_NET_HELLO_ACK_WIRE_SIZE];
|
||||
|
||||
const bool sent = lgNetHelloAckEncode(
|
||||
payload, sizeof(payload), &acknowledgement) &&
|
||||
SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_HELLO_ACK,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, requestID, payload, sizeof(payload));
|
||||
NetworkAuthCrypto::SecureZero(&acknowledgement,
|
||||
sizeof(acknowledgement));
|
||||
NetworkAuthCrypto::SecureZero(payload, sizeof(payload));
|
||||
NetworkAuthCrypto::SecureZero(serverNonce, sizeof(serverNonce));
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool SendPasswordChallenge(Connection& connection)
|
||||
{
|
||||
LGNetPasswordChallenge challenge;
|
||||
uint64_t requestID;
|
||||
{
|
||||
CSRWSharedLock guard(connection.lock);
|
||||
if (connection.closing ||
|
||||
connection.handshake != HandshakeState::WAITING_AUTH_RESPONSE)
|
||||
return false;
|
||||
challenge = connection.passwordChallenge;
|
||||
requestID = connection.handshakeRequestID;
|
||||
}
|
||||
|
||||
const size_t payloadSize = lgNetPasswordChallengeSize(&challenge);
|
||||
uint8_t payload[LG_NET_PASSWORD_CHALLENGE_HEADER_SIZE +
|
||||
LG_NET_PASSWORD_SALT_MAX_LENGTH +
|
||||
LG_NET_PASSWORD_CHALLENGE_MAX_LENGTH];
|
||||
|
||||
const bool sent = payloadSize &&
|
||||
lgNetPasswordChallengeEncode(payload, payloadSize, &challenge) &&
|
||||
SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_AUTH_CHALLENGE,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, requestID, payload,
|
||||
static_cast<uint32_t>(payloadSize));
|
||||
NetworkAuthCrypto::SecureZero(payload, sizeof(payload));
|
||||
NetworkAuthCrypto::SecureZero(&challenge, sizeof(challenge));
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool PublishCapabilities(Connection& connection)
|
||||
{
|
||||
LGNetCapability selected[NETWORK_SERVICE_COUNT] = {};
|
||||
uint64_t requestID;
|
||||
{
|
||||
CSRWSharedLock guard(connection.lock);
|
||||
if (connection.closing ||
|
||||
connection.handshake !=
|
||||
HandshakeState::PUBLISHING_CAPABILITIES ||
|
||||
connection.session)
|
||||
return false;
|
||||
memcpy(selected, connection.selectedCapabilities, sizeof(selected));
|
||||
requestID = connection.handshakeRequestID;
|
||||
}
|
||||
|
||||
for (LGNetService service = LG_NET_SERVICE_RECOVERY;
|
||||
service < LG_NET_SERVICE_USB; ++service)
|
||||
{
|
||||
if (!selected[service].service)
|
||||
continue;
|
||||
|
||||
uint8_t capabilityPayload[LG_NET_CAPABILITY_WIRE_SIZE];
|
||||
if (!lgNetCapabilityEncode(capabilityPayload,
|
||||
sizeof(capabilityPayload), &selected[service]) ||
|
||||
!SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_CAPABILITY,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, requestID, capabilityPayload,
|
||||
sizeof(capabilityPayload)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_CAPABILITIES_DONE,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, requestID, nullptr, 0))
|
||||
return false;
|
||||
|
||||
std::shared_ptr<CNetworkSession> session;
|
||||
try
|
||||
{
|
||||
session = transport.CreateSession(
|
||||
connection.client, connection.generation);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
bool activated;
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
activated = !connection.closing &&
|
||||
connection.handshake == HandshakeState::PUBLISHING_CAPABILITIES &&
|
||||
!connection.session;
|
||||
if (activated)
|
||||
{
|
||||
connection.session = std::move(session);
|
||||
connection.handshakeDeadline = 0;
|
||||
NetworkAuthCrypto::SecureZero(&connection.passwordChallenge,
|
||||
sizeof(connection.passwordChallenge));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
connection.clientNonce, sizeof(connection.clientNonce));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
connection.serverNonce, sizeof(connection.serverNonce));
|
||||
connection.handshake = HandshakeState::READY;
|
||||
}
|
||||
}
|
||||
if (!activated)
|
||||
{
|
||||
transport.RemoveSession(connection.client, connection.generation);
|
||||
return false;
|
||||
}
|
||||
DEBUG_INFO("Network client %u completed the control handshake",
|
||||
connection.client);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FinishCapabilities(Connection& connection,
|
||||
const LGNetPacketView& packet)
|
||||
{
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
if (connection.closing ||
|
||||
@@ -670,70 +890,241 @@ struct CNetworkServer::Impl
|
||||
continue;
|
||||
}
|
||||
|
||||
selected[service] = capability;
|
||||
connection.selectedCapabilities[service] = capability;
|
||||
connection.selectedServices |= serviceMask;
|
||||
connection.selectedCapabilities[service] = capability;
|
||||
connection.selectedServices |= serviceMask;
|
||||
if (capability.flags & LG_NET_CAPABILITY_RELIABLE)
|
||||
connection.reliableServices |= serviceMask;
|
||||
if (capability.flags & LG_NET_CAPABILITY_DATAGRAM)
|
||||
connection.datagramServices |= serviceMask;
|
||||
++selectedCount;
|
||||
++connection.selectedCapabilityCount;
|
||||
}
|
||||
|
||||
sessionEpoch = connection.sessionEpoch;
|
||||
maxPayload = connection.maxPayload;
|
||||
maxDatagramPayload = connection.maxDatagramPayload;
|
||||
memcpy(serverNonce, connection.serverNonce, sizeof(serverNonce));
|
||||
}
|
||||
|
||||
LGNetHelloAck acknowledgement;
|
||||
lgNetHelloAckInit(&acknowledgement, sessionEpoch);
|
||||
acknowledgement.authMode = LG_NET_AUTH_NONE;
|
||||
acknowledgement.maxPayloadLength = maxPayload;
|
||||
acknowledgement.maxDatagramPayload = maxDatagramPayload;
|
||||
acknowledgement.capabilityCount = selectedCount;
|
||||
memcpy(acknowledgement.nonce, serverNonce, sizeof(serverNonce));
|
||||
|
||||
uint8_t payload[LG_NET_HELLO_ACK_WIRE_SIZE];
|
||||
if (!lgNetHelloAckEncode(payload, sizeof(payload), &acknowledgement) ||
|
||||
!SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_HELLO_ACK,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, packet.envelope.requestID, payload, sizeof(payload)))
|
||||
return false;
|
||||
|
||||
for (LGNetService service = LG_NET_SERVICE_RECOVERY;
|
||||
service < LG_NET_SERVICE_USB; ++service)
|
||||
LGNetPasswordChallenge challenge = {};
|
||||
if (settings.auth == LG_NET_AUTH_PASSWORD)
|
||||
{
|
||||
if (!selected[service].service)
|
||||
continue;
|
||||
|
||||
uint8_t capabilityPayload[LG_NET_CAPABILITY_WIRE_SIZE];
|
||||
if (!lgNetCapabilityEncode(capabilityPayload,
|
||||
sizeof(capabilityPayload), &selected[service]) ||
|
||||
!SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_CAPABILITY,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, packet.envelope.requestID, capabilityPayload,
|
||||
sizeof(capabilityPayload)))
|
||||
challenge.algorithm = LG_NET_PASSWORD_PBKDF2_HMAC_SHA256;
|
||||
challenge.rounds = NETWORK_PASSWORD_ROUNDS;
|
||||
challenge.saltLength = NETWORK_PASSWORD_SALT_SIZE;
|
||||
challenge.challengeLength = NETWORK_PASSWORD_CHALLENGE_SIZE;
|
||||
if (!NetworkAuthCrypto::RandomBytes(
|
||||
challenge.salt, challenge.saltLength) ||
|
||||
!NetworkAuthCrypto::RandomBytes(
|
||||
challenge.challenge, challenge.challengeLength))
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(&challenge, sizeof(challenge));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_CAPABILITIES_DONE,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, packet.envelope.requestID, nullptr, 0))
|
||||
return false;
|
||||
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
if (connection.closing)
|
||||
if (connection.closing ||
|
||||
connection.handshake != HandshakeState::WAITING_CAPABILITIES)
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(&challenge, sizeof(challenge));
|
||||
return false;
|
||||
connection.handshake = HandshakeState::READY;
|
||||
}
|
||||
|
||||
if (settings.auth == LG_NET_AUTH_PASSWORD)
|
||||
{
|
||||
connection.passwordChallenge = challenge;
|
||||
connection.handshake = HandshakeState::WAITING_AUTH_RESPONSE;
|
||||
}
|
||||
else
|
||||
connection.handshake = HandshakeState::PUBLISHING_CAPABILITIES;
|
||||
}
|
||||
DEBUG_INFO("Network client %u completed the control handshake",
|
||||
connection.client);
|
||||
return true;
|
||||
|
||||
NetworkAuthCrypto::SecureZero(&challenge, sizeof(challenge));
|
||||
|
||||
if (!SendHelloAcknowledgement(connection))
|
||||
return false;
|
||||
|
||||
return settings.auth == LG_NET_AUTH_PASSWORD ?
|
||||
SendPasswordChallenge(connection) : PublishCapabilities(connection);
|
||||
}
|
||||
|
||||
bool SendAuthResult(Connection& connection,
|
||||
const LGNetAuthResult& result, uint64_t requestID)
|
||||
{
|
||||
const size_t payloadSize = lgNetAuthResultSize(&result);
|
||||
uint8_t payload[LG_NET_AUTH_RESULT_HEADER_SIZE +
|
||||
LG_NET_PASSWORD_PROOF_MAX_LENGTH];
|
||||
|
||||
const bool sent = payloadSize &&
|
||||
lgNetAuthResultEncode(payload, payloadSize, &result) &&
|
||||
SendPacket(connection, LG_NET_SERVICE_CORE,
|
||||
LG_NET_CORE_MESSAGE_AUTH_RESULT,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, requestID, payload, static_cast<uint32_t>(payloadSize));
|
||||
NetworkAuthCrypto::SecureZero(payload, sizeof(payload));
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool HandleAuthResponse(Connection& connection,
|
||||
const LGNetPacketView& packet)
|
||||
{
|
||||
LGNetPasswordResponse response = {};
|
||||
if (lgNetPasswordResponseDecode(&response, packet.payload,
|
||||
packet.payloadLength) != LG_NET_PARSE_OK)
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(&response, sizeof(response));
|
||||
return false;
|
||||
}
|
||||
|
||||
LGNetPasswordChallenge challenge = {};
|
||||
uint64_t sessionEpoch = 0;
|
||||
|
||||
uint8_t clientNonce[LG_NET_HELLO_NONCE_LENGTH] = {};
|
||||
uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH] = {};
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
if (connection.closing ||
|
||||
connection.handshake != HandshakeState::WAITING_AUTH_RESPONSE ||
|
||||
packet.envelope.requestID != connection.handshakeRequestID ||
|
||||
packet.envelope.sessionEpoch != connection.sessionEpoch ||
|
||||
packet.envelope.componentEpoch != 0)
|
||||
{
|
||||
NetworkAuthCrypto::SecureZero(&response, sizeof(response));
|
||||
return false;
|
||||
}
|
||||
|
||||
connection.handshake = HandshakeState::AUTHENTICATING;
|
||||
challenge = connection.passwordChallenge;
|
||||
sessionEpoch = connection.sessionEpoch;
|
||||
memcpy(clientNonce, connection.clientNonce, sizeof(clientNonce));
|
||||
memcpy(serverNonce, connection.serverNonce, sizeof(serverNonce));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> password;
|
||||
|
||||
uint8_t derivedKey [LG_NET_PASSWORD_SHA256_LENGTH] = {};
|
||||
uint8_t expectedProof [LG_NET_PASSWORD_SHA256_LENGTH] = {};
|
||||
uint8_t serverProof [LG_NET_PASSWORD_SHA256_LENGTH] = {};
|
||||
uint8_t clientTranscript[LG_NET_PASSWORD_CLIENT_PROOF_TRANSCRIPT_MAX_SIZE] = {};
|
||||
uint8_t serverTranscript[LG_NET_PASSWORD_SERVER_PROOF_TRANSCRIPT_MAX_SIZE] = {};
|
||||
|
||||
size_t clientTranscriptSize = 0;
|
||||
size_t serverTranscriptSize = 0;
|
||||
bool cryptoError = false;
|
||||
bool accepted = false;
|
||||
|
||||
LGNetPasswordProofContext context = {};
|
||||
context.protocolVersion = LG_NET_PROTOCOL_VERSION_CURRENT;
|
||||
context.sessionEpoch = sessionEpoch;
|
||||
context.helloNonce = clientNonce;
|
||||
context.helloAckNonce = serverNonce;
|
||||
context.challenge = &challenge;
|
||||
context.response = &response;
|
||||
|
||||
const bool responseShapeValid =
|
||||
response.algorithm == LG_NET_PASSWORD_PBKDF2_HMAC_SHA256 &&
|
||||
response.nonceLength == NETWORK_PASSWORD_NONCE_SIZE &&
|
||||
response.proofLength == LG_NET_PASSWORD_SHA256_LENGTH;
|
||||
if (responseShapeValid)
|
||||
{
|
||||
if (!PasswordUTF8(settings.password, password) ||
|
||||
!NetworkAuthCrypto::PBKDF2HMACSHA256(
|
||||
password.data(), password.size(), challenge.salt,
|
||||
challenge.saltLength, challenge.rounds,
|
||||
derivedKey, sizeof(derivedKey)) ||
|
||||
!lgNetPasswordClientProofTranscriptSize(
|
||||
&context, &clientTranscriptSize) ||
|
||||
clientTranscriptSize > sizeof(clientTranscript) ||
|
||||
!lgNetPasswordClientProofTranscriptEncode(
|
||||
clientTranscript, clientTranscriptSize, &context) ||
|
||||
!NetworkAuthCrypto::HMACSHA256(
|
||||
derivedKey, sizeof(derivedKey), clientTranscript,
|
||||
clientTranscriptSize, expectedProof))
|
||||
cryptoError = true;
|
||||
else
|
||||
accepted = NetworkAuthCrypto::ConstantTimeEqual(
|
||||
expectedProof, response.proof, sizeof(expectedProof));
|
||||
|
||||
if (accepted &&
|
||||
(!lgNetPasswordServerProofTranscriptSize(
|
||||
&context, &serverTranscriptSize) ||
|
||||
serverTranscriptSize > sizeof(serverTranscript) ||
|
||||
!lgNetPasswordServerProofTranscriptEncode(
|
||||
serverTranscript, serverTranscriptSize, &context) ||
|
||||
!NetworkAuthCrypto::HMACSHA256(
|
||||
derivedKey, sizeof(derivedKey), serverTranscript,
|
||||
serverTranscriptSize, serverProof)))
|
||||
{
|
||||
accepted = false;
|
||||
cryptoError = true;
|
||||
}
|
||||
}
|
||||
|
||||
LGNetAuthResult result = {};
|
||||
if (accepted)
|
||||
{
|
||||
result.result = LG_NET_AUTH_RESULT_ACCEPTED;
|
||||
result.proofLength = LG_NET_PASSWORD_SHA256_LENGTH;
|
||||
memcpy(result.proof, serverProof, result.proofLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.result = cryptoError ?
|
||||
LG_NET_AUTH_RESULT_ERROR : LG_NET_AUTH_RESULT_REJECTED;
|
||||
result.retryAfterMs = NETWORK_AUTH_RETRY_AFTER_MS;
|
||||
}
|
||||
|
||||
bool connectionValid;
|
||||
{
|
||||
CSRWExclusiveLock guard(connection.lock);
|
||||
connectionValid = !connection.closing &&
|
||||
connection.handshake == HandshakeState::AUTHENTICATING;
|
||||
if (connectionValid)
|
||||
{
|
||||
if (accepted)
|
||||
connection.handshake = HandshakeState::PUBLISHING_CAPABILITIES;
|
||||
else
|
||||
{
|
||||
connection.handshake = HandshakeState::AUTH_FAILED;
|
||||
connection.handshakeDeadline =
|
||||
GetTickCount64() + NETWORK_AUTH_RETRY_AFTER_MS;
|
||||
}
|
||||
}
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
&connection.passwordChallenge, sizeof(connection.passwordChallenge));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
connection.clientNonce, sizeof(connection.clientNonce));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
connection.serverNonce, sizeof(connection.serverNonce));
|
||||
}
|
||||
|
||||
NetworkAuthCrypto::SecureZero(password.data(), password.size());
|
||||
NetworkAuthCrypto::SecureZero(derivedKey, sizeof(derivedKey));
|
||||
NetworkAuthCrypto::SecureZero(expectedProof, sizeof(expectedProof));
|
||||
NetworkAuthCrypto::SecureZero(serverProof, sizeof(serverProof));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
clientTranscript, sizeof(clientTranscript));
|
||||
NetworkAuthCrypto::SecureZero(
|
||||
serverTranscript, sizeof(serverTranscript));
|
||||
NetworkAuthCrypto::SecureZero(clientNonce, sizeof(clientNonce));
|
||||
NetworkAuthCrypto::SecureZero(serverNonce, sizeof(serverNonce));
|
||||
NetworkAuthCrypto::SecureZero(&challenge, sizeof(challenge));
|
||||
NetworkAuthCrypto::SecureZero(&response, sizeof(response));
|
||||
|
||||
const bool resultSent = connectionValid &&
|
||||
SendAuthResult(connection, result, packet.envelope.requestID);
|
||||
NetworkAuthCrypto::SecureZero(&result, sizeof(result));
|
||||
if (!resultSent)
|
||||
return false;
|
||||
|
||||
if (!accepted)
|
||||
{
|
||||
if (cryptoError)
|
||||
DEBUG_ERROR("Password authentication failed internally for network "
|
||||
"client %u", connection.client);
|
||||
else
|
||||
DEBUG_WARN("Network client %u failed password authentication",
|
||||
connection.client);
|
||||
return true;
|
||||
}
|
||||
|
||||
return PublishCapabilities(connection);
|
||||
}
|
||||
|
||||
static LGNetInputStatusCode InputStatusCode(InputResult result)
|
||||
@@ -1196,6 +1587,19 @@ struct CNetworkServer::Impl
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handshake == HandshakeState::WAITING_AUTH_RESPONSE)
|
||||
{
|
||||
return packet.envelope.service == LG_NET_SERVICE_CORE &&
|
||||
packet.envelope.messageType == LG_NET_CORE_MESSAGE_AUTH_RESPONSE &&
|
||||
packet.envelope.serviceVersion == LG_NET_CORE_VERSION_CURRENT &&
|
||||
packet.envelope.sessionEpoch == sessionEpoch &&
|
||||
packet.envelope.payloadLength <= maxPayload &&
|
||||
HandleAuthResponse(connection, packet);
|
||||
}
|
||||
|
||||
if (handshake != HandshakeState::READY)
|
||||
return false;
|
||||
|
||||
if (packet.envelope.sessionEpoch != sessionEpoch ||
|
||||
packet.envelope.payloadLength > maxPayload ||
|
||||
(packet.envelope.service == LG_NET_SERVICE_CORE &&
|
||||
@@ -1434,9 +1838,11 @@ struct CNetworkServer::Impl
|
||||
delete connection;
|
||||
return QUIC_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
connection->handle = event->NEW_CONNECTION.Connection;
|
||||
connection->client = owner.AllocateClientLocked();
|
||||
connection->generation = 1;
|
||||
connection->handle = event->NEW_CONNECTION.Connection;
|
||||
connection->client = owner.AllocateClientLocked();
|
||||
connection->generation = 1;
|
||||
connection->handshakeDeadline =
|
||||
GetTickCount64() + NETWORK_HANDSHAKE_TIMEOUT_MS;
|
||||
if (!connection->client)
|
||||
{
|
||||
delete connection;
|
||||
@@ -1608,6 +2014,38 @@ struct CNetworkServer::Impl
|
||||
return QUIC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void Process()
|
||||
{
|
||||
const uint64_t now = GetTickCount64();
|
||||
CSRWSharedLock guard(lock);
|
||||
if (stopping || !api)
|
||||
return;
|
||||
|
||||
for (Connection * connection : connections)
|
||||
{
|
||||
HQUIC handle = nullptr;
|
||||
uint32_t client = 0;
|
||||
{
|
||||
CSRWExclusiveLock connectionGuard(connection->lock);
|
||||
if (!connection->closing && connection->handshakeDeadline &&
|
||||
now >= connection->handshakeDeadline)
|
||||
{
|
||||
connection->closing = true;
|
||||
handle = connection->handle;
|
||||
client = connection->client;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handle)
|
||||
continue;
|
||||
|
||||
DEBUG_WARN("Closing network client %u after an incomplete handshake",
|
||||
client);
|
||||
api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE,
|
||||
NETWORK_ERROR_HANDSHAKE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
bool Start()
|
||||
{
|
||||
{
|
||||
@@ -1617,14 +2055,31 @@ struct CNetworkServer::Impl
|
||||
stopping = false;
|
||||
}
|
||||
|
||||
if (settings.auth != LG_NET_AUTH_NONE)
|
||||
if ((settings.auth != LG_NET_AUTH_NONE &&
|
||||
settings.auth != LG_NET_AUTH_PASSWORD) ||
|
||||
(settings.auth == LG_NET_AUTH_PASSWORD &&
|
||||
settings.password.empty()) ||
|
||||
(settings.auth == LG_NET_AUTH_NONE && !settings.password.empty()))
|
||||
{
|
||||
DEBUG_ERROR("Network password authentication is configured but the "
|
||||
"password proof provider is not implemented; refusing to listen");
|
||||
DEBUG_ERROR("The network authentication settings are inconsistent");
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.auth == LG_NET_AUTH_PASSWORD)
|
||||
{
|
||||
std::vector<uint8_t> password;
|
||||
|
||||
const bool valid = PasswordUTF8(settings.password, password);
|
||||
NetworkAuthCrypto::SecureZero(password.data(), password.size());
|
||||
if (!valid)
|
||||
{
|
||||
DEBUG_ERROR("The configured network password is not valid UTF-16");
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!drainedEvent)
|
||||
drainedEvent = CreateEventW(nullptr, TRUE, TRUE, nullptr);
|
||||
if (!drainedEvent || QUIC_FAILED(MsQuicOpen2(&api)))
|
||||
@@ -1801,3 +2256,9 @@ void CNetworkServer::Stop()
|
||||
if (m_impl)
|
||||
m_impl->Stop();
|
||||
}
|
||||
|
||||
void CNetworkServer::Process()
|
||||
{
|
||||
if (m_impl)
|
||||
m_impl->Process();
|
||||
}
|
||||
|
||||
@@ -41,4 +41,5 @@ public:
|
||||
|
||||
bool Start();
|
||||
void Stop();
|
||||
void Process();
|
||||
};
|
||||
|
||||
@@ -121,13 +121,18 @@ bool CNetworkTransport::Setup(size_t alignment)
|
||||
ITransport::ProcessResult CNetworkTransport::Process(
|
||||
ITransportEvents& events)
|
||||
{
|
||||
CNetworkServer * server;
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (m_state != State::INITIALIZED && m_state != State::READY)
|
||||
return m_state == State::STOPPED ?
|
||||
ProcessResult::FAILURE : ProcessResult::OK;
|
||||
server = m_server.get();
|
||||
}
|
||||
|
||||
if (server)
|
||||
server->Process();
|
||||
|
||||
NetworkEvent event;
|
||||
for (unsigned i = 0; i < EVENT_DRAIN_LIMIT && m_events.Take(event); ++i)
|
||||
{
|
||||
|
||||
183
idd/LGIdd/transport/network/NetworkAuthCrypto.cpp
Normal file
183
idd/LGIdd/transport/network/NetworkAuthCrypto.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program 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 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/network/NetworkAuthCrypto.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace
|
||||
{
|
||||
static const std::uint8_t EMPTY_INPUT = 0;
|
||||
|
||||
PUCHAR InputBytes(const void * data, std::size_t size) noexcept
|
||||
{
|
||||
return const_cast<PUCHAR>(static_cast<const UCHAR *>(
|
||||
size ? data : &EMPTY_INPUT));
|
||||
}
|
||||
|
||||
bool OpenHMACSHA256(BCRYPT_ALG_HANDLE& algorithm) noexcept
|
||||
{
|
||||
return BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&algorithm,
|
||||
BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG));
|
||||
}
|
||||
}
|
||||
|
||||
namespace NetworkAuthCrypto
|
||||
{
|
||||
bool RandomBytes(void * data, std::size_t size) noexcept
|
||||
{
|
||||
if (size && !data)
|
||||
return false;
|
||||
|
||||
auto * output = static_cast<std::uint8_t *>(data);
|
||||
const std::size_t totalSize = size;
|
||||
while (size)
|
||||
{
|
||||
const ULONG chunk = size > ULONG_MAX ? ULONG_MAX :
|
||||
static_cast<ULONG>(size);
|
||||
if (!BCRYPT_SUCCESS(BCryptGenRandom(nullptr, output, chunk,
|
||||
BCRYPT_USE_SYSTEM_PREFERRED_RNG)))
|
||||
{
|
||||
SecureZero(data, totalSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
output += chunk;
|
||||
size -= chunk;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PBKDF2HMACSHA256(const void * password, std::size_t passwordSize,
|
||||
const void * salt, std::size_t saltSize, std::uint32_t iterations,
|
||||
void * key, std::size_t keySize) noexcept
|
||||
{
|
||||
if ((passwordSize && !password) || (saltSize && !salt) || !iterations ||
|
||||
!key || !keySize || passwordSize > INT_MAX || saltSize > INT_MAX ||
|
||||
keySize > INT_MAX || iterations > INT_MAX)
|
||||
return false;
|
||||
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
if (!OpenHMACSHA256(algorithm))
|
||||
{
|
||||
SecureZero(key, keySize);
|
||||
return false;
|
||||
}
|
||||
|
||||
const NTSTATUS status = BCryptDeriveKeyPBKDF2(algorithm,
|
||||
InputBytes(password, passwordSize), static_cast<ULONG>(passwordSize),
|
||||
InputBytes(salt, saltSize), static_cast<ULONG>(saltSize), iterations,
|
||||
static_cast<PUCHAR>(key), static_cast<ULONG>(keySize), 0);
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
if (BCRYPT_SUCCESS(status))
|
||||
return true;
|
||||
|
||||
SecureZero(key, keySize);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HMACSHA256(const void * key, std::size_t keySize,
|
||||
const void * data, std::size_t size,
|
||||
std::uint8_t output[SHA256_SIZE]) noexcept
|
||||
{
|
||||
if ((keySize && !key) || (size && !data) || keySize > INT_MAX || !output)
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
PUCHAR hashObject = nullptr;
|
||||
ULONG hashSize = 0;
|
||||
ULONG propertySize = 0;
|
||||
const auto * input = static_cast<const std::uint8_t *>(data);
|
||||
|
||||
if (!OpenHMACSHA256(algorithm) ||
|
||||
!BCRYPT_SUCCESS(BCryptGetProperty(algorithm, BCRYPT_OBJECT_LENGTH,
|
||||
reinterpret_cast<PUCHAR>(&hashSize), sizeof(hashSize),
|
||||
&propertySize, 0)) ||
|
||||
propertySize != sizeof(hashSize) || !hashSize)
|
||||
goto cleanup;
|
||||
|
||||
hashObject = static_cast<PUCHAR>(
|
||||
HeapAlloc(GetProcessHeap(), 0, hashSize));
|
||||
if (!hashObject ||
|
||||
!BCRYPT_SUCCESS(BCryptCreateHash(algorithm, &hash, hashObject,
|
||||
hashSize, InputBytes(key, keySize), static_cast<ULONG>(keySize), 0)))
|
||||
goto cleanup;
|
||||
|
||||
while (size)
|
||||
{
|
||||
const ULONG chunk = size > ULONG_MAX ? ULONG_MAX :
|
||||
static_cast<ULONG>(size);
|
||||
if (!BCRYPT_SUCCESS(BCryptHashData(
|
||||
hash, const_cast<PUCHAR>(input), chunk, 0)))
|
||||
goto cleanup;
|
||||
|
||||
input += chunk;
|
||||
size -= chunk;
|
||||
}
|
||||
|
||||
result = BCRYPT_SUCCESS(BCryptFinishHash(
|
||||
hash, output, static_cast<ULONG>(SHA256_SIZE), 0));
|
||||
|
||||
cleanup:
|
||||
if (hash)
|
||||
BCryptDestroyHash(hash);
|
||||
if (hashObject)
|
||||
{
|
||||
SecureZeroMemory(hashObject, hashSize);
|
||||
HeapFree(GetProcessHeap(), 0, hashObject);
|
||||
}
|
||||
if (algorithm)
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
if (!result)
|
||||
SecureZero(output, SHA256_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ConstantTimeEqual(
|
||||
const void * left, const void * right, std::size_t size) noexcept
|
||||
{
|
||||
if (size && (!left || !right))
|
||||
return false;
|
||||
|
||||
const volatile std::uint8_t * leftBytes =
|
||||
static_cast<const volatile std::uint8_t *>(left);
|
||||
const volatile std::uint8_t * rightBytes =
|
||||
static_cast<const volatile std::uint8_t *>(right);
|
||||
volatile std::uint8_t difference = 0;
|
||||
for (std::size_t index = 0; index < size; ++index)
|
||||
{
|
||||
difference = static_cast<std::uint8_t>(difference |
|
||||
static_cast<std::uint8_t>(leftBytes[index] ^ rightBytes[index]));
|
||||
}
|
||||
return difference == 0;
|
||||
}
|
||||
|
||||
void SecureZero(void * data, std::size_t size) noexcept
|
||||
{
|
||||
if (data && size)
|
||||
SecureZeroMemory(data, size);
|
||||
}
|
||||
}
|
||||
40
idd/LGIdd/transport/network/NetworkAuthCrypto.h
Normal file
40
idd/LGIdd/transport/network/NetworkAuthCrypto.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program 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 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace NetworkAuthCrypto
|
||||
{
|
||||
static constexpr std::size_t SHA256_SIZE = 32;
|
||||
|
||||
bool RandomBytes(void * data, std::size_t size) noexcept;
|
||||
bool PBKDF2HMACSHA256(const void * password, std::size_t passwordSize,
|
||||
const void * salt, std::size_t saltSize, std::uint32_t iterations,
|
||||
void * key, std::size_t keySize) noexcept;
|
||||
bool HMACSHA256(const void * key, std::size_t keySize,
|
||||
const void * data, std::size_t size,
|
||||
std::uint8_t output[SHA256_SIZE]) noexcept;
|
||||
bool ConstantTimeEqual(
|
||||
const void * left, const void * right, std::size_t size) noexcept;
|
||||
void SecureZero(void * data, std::size_t size) noexcept;
|
||||
}
|
||||
Reference in New Issue
Block a user