From 6dfba7519c35b28267b56d3adb7d4c06c7c0361f Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 30 Sep 2026 19:56:54 +1000 Subject: [PATCH] [client/idd] network: add password authentication --- client/transports/Network/CMakeLists.txt | 2 + client/transports/Network/auth_crypto.h | 38 + client/transports/Network/connection.c | 416 ++++++++++- client/transports/Network/network.c | 4 +- .../Network/platform/linux/auth_crypto.c | 112 +++ .../Network/platform/windows/auth_crypto.c | 166 +++++ common/include/common/option.h | 5 +- common/src/option.c | 53 +- idd/LGCommon/LGCommon.vcxproj | 1 + idd/LGCommon/LGCommon.vcxproj.filters | 3 + idd/LGIdd/LGIdd.vcxproj | 2 + idd/LGIdd/LGIdd.vcxproj.filters | 6 + .../transport/network/CNetworkServer.cpp | 655 +++++++++++++++--- idd/LGIdd/transport/network/CNetworkServer.h | 1 + .../transport/network/CNetworkTransport.cpp | 5 + .../transport/network/NetworkAuthCrypto.cpp | 183 +++++ .../transport/network/NetworkAuthCrypto.h | 40 ++ 17 files changed, 1546 insertions(+), 146 deletions(-) create mode 100644 client/transports/Network/auth_crypto.h create mode 100644 client/transports/Network/platform/linux/auth_crypto.c create mode 100644 client/transports/Network/platform/windows/auth_crypto.c create mode 100644 idd/LGIdd/transport/network/NetworkAuthCrypto.cpp create mode 100644 idd/LGIdd/transport/network/NetworkAuthCrypto.h diff --git a/client/transports/Network/CMakeLists.txt b/client/transports/Network/CMakeLists.txt index 9931e9b8..faff2de0 100644 --- a/client/transports/Network/CMakeLists.txt +++ b/client/transports/Network/CMakeLists.txt @@ -14,12 +14,14 @@ set(TRANSPORT_NETWORK_SOURCES if(WIN32) list(APPEND TRANSPORT_NETWORK_SOURCES + platform/windows/auth_crypto.c platform/windows/fingerprint.c platform/windows/platform.c ) else() find_package(OpenSSL REQUIRED COMPONENTS Crypto) list(APPEND TRANSPORT_NETWORK_SOURCES + platform/linux/auth_crypto.c platform/linux/fingerprint.c platform/linux/platform.c ) diff --git a/client/transports/Network/auth_crypto.h b/client/transports/Network/auth_crypto.h new file mode 100644 index 00000000..8066918b --- /dev/null +++ b/client/transports/Network/auth_crypto.h @@ -0,0 +1,38 @@ +/** + * 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 +#include +#include + +#define NETWORK_AUTH_SHA256_SIZE 32U + +bool networkAuthRandomBytes(void * data, size_t size); +bool networkAuthPBKDF2HMACSHA256(const void * password, + size_t passwordSize, const void * salt, size_t saltSize, + uint32_t iterations, void * key, size_t keySize); +bool networkAuthHMACSHA256(const void * key, size_t keySize, + const void * data, size_t size, + uint8_t output[NETWORK_AUTH_SHA256_SIZE]); +bool networkAuthConstantTimeEqual( + const void * left, const void * right, size_t size); +void networkAuthSecureZero(void * data, size_t size); diff --git a/client/transports/Network/connection.c b/client/transports/Network/connection.c index 1016bab0..46dd134a 100644 --- a/client/transports/Network/connection.c +++ b/client/transports/Network/connection.c @@ -19,12 +19,14 @@ */ #include "connection.h" +#include "auth_crypto.h" #include "fingerprint.h" #include "common/debug.h" #include "common/event.h" #include "common/locking.h" +#include #include #include #include @@ -41,18 +43,26 @@ #include #include -#define NETWORK_CONNECT_POLL_MS 50U -#define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U -#define NETWORK_IDLE_TIMEOUT_MS 10000U -#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U -#define NETWORK_INITIAL_RTT_MS 1U -#define NETWORK_MAX_PEER_STREAMS 16U -#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) -#define NETWORK_MAX_CONTROL_BUFFER \ +#define NETWORK_CONNECT_POLL_MS 50U +#define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U +#define NETWORK_IDLE_TIMEOUT_MS 10000U +#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U +#define NETWORK_INITIAL_RTT_MS 1U +#define NETWORK_MAX_PEER_STREAMS 16U +#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) +#define NETWORK_MAX_CONTROL_BUFFER \ (2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) -#define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U -#define NETWORK_HELLO_REQUEST_ID UINT64_C(1) -#define NETWORK_SEQUENCE_CONNECTION_EPOCH UINT64_C(1) +#define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U +#define NETWORK_HELLO_REQUEST_ID UINT64_C(1) +#define NETWORK_SEQUENCE_CONNECTION_EPOCH UINT64_C(1) +#define NETWORK_AUTH_PBKDF2_ROUNDS_MIN 10000U +#define NETWORK_AUTH_PBKDF2_ROUNDS_MAX 1000000U +#define NETWORK_AUTH_SALT_LENGTH_MIN 16U +#define NETWORK_AUTH_CHALLENGE_LENGTH_MIN 16U +#define NETWORK_AUTH_RESPONSE_NONCE_LENGTH 32U + +_Static_assert(NETWORK_AUTH_SHA256_SIZE == LG_NET_PASSWORD_SHA256_LENGTH, + "Network authentication SHA-256 sizes differ"); struct NetworkSend { @@ -103,8 +113,14 @@ struct NetworkConnection uint64_t datagramSendSequence; uint64_t receiveSequence; LGNetSequenceTracker datagramReceive; + uint16_t protocolVersion; + LGNetAuthMode authMode; bool helloAcknowledged; bool authenticationComplete; + bool authChallengeReceived; + bool authResponseSent; + bool authResultReceived; + bool authKeyValid; bool capabilitiesComplete; bool connectFailed; uint32_t expectedCapabilities; @@ -120,6 +136,12 @@ struct NetworkConnection uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U]; uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U]; + uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + LGNetPasswordChallenge authChallenge; + LGNetPasswordResponse authResponse; + uint8_t authKey[LG_NET_PASSWORD_SHA256_LENGTH]; + LGNetStreamParser receiveParser; uint8_t * receiveData; uint8_t * receivePayload; @@ -174,6 +196,32 @@ static bool networkCapabilityOfferValid( return true; } +static bool networkBytesNonzero(const uint8_t * data, size_t size) +{ + if (!data) + return false; + + uint8_t value = 0; + for (size_t index = 0; index < size; ++index) + value |= data[index]; + return value != 0; +} + +static void networkConnectionClearAuthProofNL(NetworkConnection * this) +{ + networkAuthSecureZero( + this->clientHelloNonce, sizeof(this->clientHelloNonce)); + networkAuthSecureZero( + this->serverHelloNonce, sizeof(this->serverHelloNonce)); + networkAuthSecureZero(&this->authChallenge, sizeof(this->authChallenge)); + networkAuthSecureZero(&this->authResponse, sizeof(this->authResponse)); + networkAuthSecureZero(this->authKey, sizeof(this->authKey)); + this->authChallengeReceived = false; + this->authResponseSent = false; + this->authResultReceived = false; + this->authKeyValid = false; +} + static void networkConnectionSetState(NetworkConnection * this, bool connected, LG_TransportStatus reason) { @@ -200,6 +248,7 @@ static void networkConnectionFail(NetworkConnection * this, } reason = this->connectStatus == LG_TRANSPORT_OK ? status : this->connectStatus; + networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); networkConnectionSetState(this, false, reason); @@ -298,14 +347,21 @@ static bool networkConnectionSendHello(NetworkConnection * this) LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE); uint8_t payload[LG_NET_HELLO_WIRE_SIZE]; - if (!lgNetHelloEncode(payload, sizeof(payload), &hello)) - return false; + bool result = false; + if (!networkAuthRandomBytes(hello.nonce, sizeof(hello.nonce)) || + !networkBytesNonzero(hello.nonce, sizeof(hello.nonce)) || + !lgNetHelloEncode(payload, sizeof(payload), &hello)) + goto cleanup; + + LG_LOCK(this->lock); + memcpy(this->clientHelloNonce, hello.nonce, sizeof(hello.nonce)); + LG_UNLOCK(this->lock); if (!networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_HELLO, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, payload, sizeof(payload), true)) - return false; + goto cleanup; for (LGNetService service = LG_NET_SERVICE_RECOVERY; service < LG_NET_SERVICE_USB; ++service) @@ -320,13 +376,24 @@ static bool networkConnectionSendHello(NetworkConnection * this) LG_NET_CORE_MESSAGE_CAPABILITY, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, capabilityPayload, sizeof(capabilityPayload), true)) - return false; + goto cleanup; } - return networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, + result = networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_CAPABILITIES_DONE, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, NULL, 0, true); + +cleanup: + networkAuthSecureZero(payload, sizeof(payload)); + networkAuthSecureZero(&hello, sizeof(hello)); + if (!result) + { + LG_LOCK(this->lock); + networkConnectionClearAuthProofNL(this); + LG_UNLOCK(this->lock); + } + return result; } static void networkConnectionReady(NetworkConnection * this) @@ -431,6 +498,16 @@ static bool networkConnectionRequiredCapabilitiesSelected( return true; } +static bool networkConnectionReadyToActivateNL( + const NetworkConnection * this) +{ + return this->helloAcknowledged && + this->authenticationComplete && + this->capabilitiesComplete && + this->receivedCapabilities == this->expectedCapabilities && + networkConnectionRequiredCapabilitiesSelected(this); +} + static void networkConnectionSetSessionFeatures(NetworkConnection * this) { const uint16_t services = @@ -443,6 +520,264 @@ static void networkConnectionSetSessionFeatures(NetworkConnection * this) this->session.features |= LG_TRANSPORT_FEATURE_AUDIO; } +static bool networkConnectionHandleAuthChallenge(NetworkConnection * this, + const LGNetPacketView * packet) +{ + const LGNetEnvelopeFlags responseFlags = + LG_NET_ENVELOPE_RESPONSE | + LG_NET_ENVELOPE_FINAL | + LG_NET_ENVELOPE_RELIABLE; + LGNetPasswordChallenge challenge; + if (packet->envelope.flags != responseFlags || + packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || + packet->envelope.componentEpoch != 0 || + packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || + lgNetPasswordChallengeDecode(&challenge, + packet->payload, packet->payloadLength) != LG_NET_PARSE_OK || + challenge.algorithm != LG_NET_PASSWORD_PBKDF2_HMAC_SHA256 || + challenge.rounds < NETWORK_AUTH_PBKDF2_ROUNDS_MIN || + challenge.rounds > NETWORK_AUTH_PBKDF2_ROUNDS_MAX || + challenge.saltLength < NETWORK_AUTH_SALT_LENGTH_MIN || + challenge.challengeLength < NETWORK_AUTH_CHALLENGE_LENGTH_MIN) + return false; + + uint16_t protocolVersion; + uint64_t sessionEpoch; + uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + LG_LOCK(this->lock); + const bool validState = this->password && *this->password && + this->helloAcknowledged && + this->authMode == LG_NET_AUTH_PASSWORD && + !this->authenticationComplete && + !this->authChallengeReceived && + !this->authResponseSent && + packet->envelope.sessionEpoch == this->sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + if (validState) + { + this->authChallengeReceived = true; + protocolVersion = this->protocolVersion; + sessionEpoch = this->sessionEpoch; + memcpy(clientHelloNonce, this->clientHelloNonce, + sizeof(clientHelloNonce)); + memcpy(serverHelloNonce, this->serverHelloNonce, + sizeof(serverHelloNonce)); + } + LG_UNLOCK(this->lock); + if (!validState) + return false; + + LGNetPasswordResponse response; + uint8_t key[LG_NET_PASSWORD_SHA256_LENGTH] = { 0 }; + uint8_t transcript + [LG_NET_PASSWORD_CLIENT_PROOF_TRANSCRIPT_MAX_SIZE] = { 0 }; + uint8_t payload[LG_NET_PASSWORD_RESPONSE_HEADER_SIZE + + LG_NET_PASSWORD_NONCE_MAX_LENGTH + + LG_NET_PASSWORD_PROOF_MAX_LENGTH] = { 0 }; + memset(&response, 0, sizeof(response)); + response.algorithm = LG_NET_PASSWORD_PBKDF2_HMAC_SHA256; + response.nonceLength = NETWORK_AUTH_RESPONSE_NONCE_LENGTH; + response.proofLength = LG_NET_PASSWORD_SHA256_LENGTH; + + LGNetPasswordProofContext context = + { + .protocolVersion = protocolVersion, + .sessionEpoch = sessionEpoch, + .helloNonce = clientHelloNonce, + .helloAckNonce = serverHelloNonce, + .challenge = &challenge, + .response = &response, + }; + size_t transcriptSize = 0; + size_t payloadSize = 0; + bool stored = false; + bool sent = false; + if (!networkAuthRandomBytes(response.nonce, response.nonceLength) || + !networkBytesNonzero(response.nonce, response.nonceLength) || + !networkAuthPBKDF2HMACSHA256(this->password, + strlen(this->password), challenge.salt, challenge.saltLength, + challenge.rounds, key, sizeof(key)) || + !lgNetPasswordClientProofTranscriptSize(&context, &transcriptSize) || + transcriptSize > sizeof(transcript) || + !lgNetPasswordClientProofTranscriptEncode( + transcript, transcriptSize, &context) || + !networkAuthHMACSHA256(key, sizeof(key), transcript, + transcriptSize, response.proof)) + goto cleanup; + + payloadSize = lgNetPasswordResponseSize(&response); + if (!payloadSize || payloadSize > sizeof(payload) || + !lgNetPasswordResponseEncode(payload, payloadSize, &response)) + goto cleanup; + + LG_LOCK(this->lock); + stored = this->authChallengeReceived && + !this->authResponseSent && + !this->authenticationComplete && + packet->envelope.sessionEpoch == this->sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + if (stored) + { + this->authChallenge = challenge; + this->authResponse = response; + memcpy(this->authKey, key, sizeof(key)); + this->authResponseSent = true; + this->authKeyValid = true; + } + LG_UNLOCK(this->lock); + if (!stored) + goto cleanup; + + sent = networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_AUTH_RESPONSE, LG_NET_CORE_VERSION_CURRENT, + LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, + payload, (uint32_t)payloadSize, true); + +cleanup: + if (stored && !sent) + { + LG_LOCK(this->lock); + networkConnectionClearAuthProofNL(this); + LG_UNLOCK(this->lock); + } + networkAuthSecureZero(payload, sizeof(payload)); + networkAuthSecureZero(transcript, sizeof(transcript)); + networkAuthSecureZero(key, sizeof(key)); + networkAuthSecureZero(&response, sizeof(response)); + networkAuthSecureZero(&challenge, sizeof(challenge)); + networkAuthSecureZero(clientHelloNonce, sizeof(clientHelloNonce)); + networkAuthSecureZero(serverHelloNonce, sizeof(serverHelloNonce)); + return sent; +} + +static bool networkConnectionHandleAuthResult(NetworkConnection * this, + const LGNetPacketView * packet) +{ + const LGNetEnvelopeFlags responseFlags = + LG_NET_ENVELOPE_RESPONSE | + LG_NET_ENVELOPE_FINAL | + LG_NET_ENVELOPE_RELIABLE; + LGNetAuthResult result; + if (packet->envelope.flags != responseFlags || + packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || + packet->envelope.componentEpoch != 0 || + packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || + lgNetAuthResultDecode(&result, + packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) + return false; + + if (result.result != LG_NET_AUTH_RESULT_ACCEPTED) + { + DEBUG_ERROR("Network password authentication was rejected " + "(result %u, retry after %u ms)", + (unsigned)result.result, (unsigned)result.retryAfterMs); + networkAuthSecureZero(&result, sizeof(result)); + return false; + } + if (result.retryAfterMs || + result.proofLength != LG_NET_PASSWORD_SHA256_LENGTH) + { + networkAuthSecureZero(&result, sizeof(result)); + return false; + } + + uint16_t protocolVersion; + uint64_t sessionEpoch; + uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; + LGNetPasswordChallenge challenge; + LGNetPasswordResponse response; + uint8_t key[LG_NET_PASSWORD_SHA256_LENGTH]; + LG_LOCK(this->lock); + const bool validState = this->helloAcknowledged && + this->authMode == LG_NET_AUTH_PASSWORD && + !this->authenticationComplete && + this->authChallengeReceived && + this->authResponseSent && + !this->authResultReceived && + this->authKeyValid && + packet->envelope.sessionEpoch == this->sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + if (validState) + { + this->authResultReceived = true; + protocolVersion = this->protocolVersion; + sessionEpoch = this->sessionEpoch; + memcpy(clientHelloNonce, this->clientHelloNonce, + sizeof(clientHelloNonce)); + memcpy(serverHelloNonce, this->serverHelloNonce, + sizeof(serverHelloNonce)); + challenge = this->authChallenge; + response = this->authResponse; + memcpy(key, this->authKey, sizeof(key)); + } + LG_UNLOCK(this->lock); + if (!validState) + { + networkAuthSecureZero(&result, sizeof(result)); + return false; + } + + LGNetPasswordProofContext context = + { + .protocolVersion = protocolVersion, + .sessionEpoch = sessionEpoch, + .helloNonce = clientHelloNonce, + .helloAckNonce = serverHelloNonce, + .challenge = &challenge, + .response = &response, + }; + uint8_t transcript + [LG_NET_PASSWORD_SERVER_PROOF_TRANSCRIPT_MAX_SIZE] = { 0 }; + uint8_t expectedProof[LG_NET_PASSWORD_SHA256_LENGTH] = { 0 }; + size_t transcriptSize = 0; + bool verified = false; + if (lgNetPasswordServerProofTranscriptSize(&context, &transcriptSize) && + transcriptSize <= sizeof(transcript) && + lgNetPasswordServerProofTranscriptEncode( + transcript, transcriptSize, &context) && + networkAuthHMACSHA256(key, sizeof(key), transcript, + transcriptSize, expectedProof)) + verified = networkAuthConstantTimeEqual( + expectedProof, result.proof, sizeof(expectedProof)); + + bool ready = false; + bool stateValid = false; + LG_LOCK(this->lock); + stateValid = this->authResultReceived && + !this->authenticationComplete && + this->authResponseSent && + this->authKeyValid && + packet->envelope.sessionEpoch == this->sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + if (verified && stateValid) + { + this->authenticationComplete = true; + ready = + networkConnectionReadyToActivateNL(this); + if (ready) + networkConnectionSetSessionFeatures(this); + } + networkConnectionClearAuthProofNL(this); + LG_UNLOCK(this->lock); + + if (!verified) + DEBUG_ERROR("Network server password proof validation failed"); + if (ready) + networkConnectionReady(this); + + networkAuthSecureZero(expectedProof, sizeof(expectedProof)); + networkAuthSecureZero(transcript, sizeof(transcript)); + networkAuthSecureZero(key, sizeof(key)); + networkAuthSecureZero(&response, sizeof(response)); + networkAuthSecureZero(&challenge, sizeof(challenge)); + networkAuthSecureZero(clientHelloNonce, sizeof(clientHelloNonce)); + networkAuthSecureZero(serverHelloNonce, sizeof(serverHelloNonce)); + networkAuthSecureZero(&result, sizeof(result)); + return verified && stateValid; +} + static bool networkConnectionHandleCore(NetworkConnection * this, const LGNetPacketView * packet) { @@ -461,36 +796,54 @@ static bool networkConnectionHandleCore(NetworkConnection * this, case LG_NET_CORE_MESSAGE_HELLO_ACK: { LGNetHelloAck acknowledgement; + const LGNetAuthMode expectedAuthMode = + this->password && *this->password ? + LG_NET_AUTH_PASSWORD : LG_NET_AUTH_NONE; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || + packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || lgNetHelloAckDecode(&acknowledgement, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK || acknowledgement.protocolVersion != LG_NET_PROTOCOL_VERSION_CURRENT || - acknowledgement.authMode != LG_NET_AUTH_NONE || + acknowledgement.authMode != expectedAuthMode || + acknowledgement.flags || + acknowledgement.capabilityCount > + this->offeredCapabilityCount || acknowledgement.maxPayloadLength > NETWORK_MAX_CONTROL_PAYLOAD || acknowledgement.maxDatagramPayload > NETWORK_MAX_DATAGRAM_PAYLOAD || + !networkBytesNonzero( + acknowledgement.nonce, sizeof(acknowledgement.nonce)) || packet->envelope.sessionEpoch != acknowledgement.sessionEpoch) return false; LG_LOCK(this->lock); - if (this->helloAcknowledged) + if (this->helloAcknowledged || + !networkBytesNonzero( + this->clientHelloNonce, sizeof(this->clientHelloNonce))) { LG_UNLOCK(this->lock); return false; } this->helloAcknowledged = true; - this->authenticationComplete = true; + this->authenticationComplete = + acknowledgement.authMode == LG_NET_AUTH_NONE; this->expectedCapabilities = acknowledgement.capabilityCount; + this->protocolVersion = acknowledgement.protocolVersion; + this->authMode = acknowledgement.authMode; this->sessionEpoch = acknowledgement.sessionEpoch; this->maxPayloadLength = acknowledgement.maxPayloadLength; this->maxDatagramPayload = acknowledgement.maxDatagramPayload; + memcpy(this->serverHelloNonce, acknowledgement.nonce, + sizeof(this->serverHelloNonce)); const bool sequenceReady = lgNetSequenceTrackerInit( &this->datagramReceive, acknowledgement.sessionEpoch, NETWORK_SEQUENCE_CONNECTION_EPOCH, 64, false); + if (this->authenticationComplete) + networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); return sequenceReady; } @@ -514,26 +867,26 @@ static bool networkConnectionHandleCore(NetworkConnection * this, bool ready; LG_LOCK(this->lock); - ready = !this->capabilitiesComplete && + const bool valid = !this->capabilitiesComplete && this->helloAcknowledged && - this->authenticationComplete && this->receivedCapabilities == this->expectedCapabilities && networkConnectionRequiredCapabilitiesSelected(this); - this->capabilitiesComplete = ready; + this->capabilitiesComplete = valid; + ready = + valid && networkConnectionReadyToActivateNL(this); if (ready) networkConnectionSetSessionFeatures(this); LG_UNLOCK(this->lock); if (ready) networkConnectionReady(this); - return ready; + return valid; } case LG_NET_CORE_MESSAGE_AUTH_CHALLENGE: - DEBUG_ERROR("The server requested unsupported password authentication"); - return false; + return networkConnectionHandleAuthChallenge(this, packet); case LG_NET_CORE_MESSAGE_AUTH_RESULT: - return false; + return networkConnectionHandleAuthResult(this, packet); default: return false; @@ -1109,6 +1462,8 @@ void networkConnectionDestroy(NetworkConnection ** connection) free(this->receivePayload); free(this->receiveData); free(this->endpoint); + if (this->password) + networkAuthSecureZero(this->password, strlen(this->password)); free(this->password); free(this->address); free(this); @@ -1121,11 +1476,6 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, { if (!this || !session || this->connection) return LG_TRANSPORT_ERROR; - if (this->password && *this->password) - { - DEBUG_ERROR("network:password requires a future password proof provider"); - return LG_TRANSPORT_ERROR; - } if (cancelled && cancelled(cancelledOpaque)) return LG_TRANSPORT_DISCONNECTED; @@ -1141,6 +1491,8 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, this->sendSequence = 0; this->datagramSendSequence = 0; this->receiveSequence = 0; + this->protocolVersion = 0; + this->authMode = LG_NET_AUTH_NONE; this->helloAcknowledged = false; this->authenticationComplete = false; this->capabilitiesComplete = false; @@ -1154,6 +1506,7 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, this->maxDatagramPayload = 0; this->maxDatagramSendLength = 0; this->datagramSendEnabled = false; + networkConnectionClearAuthProofNL(this); memset(this->serviceVersions, 0, sizeof(this->serviceVersions)); memset(this->serviceMaxPayload, 0, sizeof(this->serviceMaxPayload)); memset(this->serviceMaxDatagram, 0, sizeof(this->serviceMaxDatagram)); @@ -1227,6 +1580,7 @@ void networkConnectionCancel(NetworkConnection * this) atomic_store_explicit(&this->sessionValid, false, memory_order_release); this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); + networkConnectionClearAuthProofNL(this); if (this->connecting) { this->connectStatus = LG_TRANSPORT_DISCONNECTED; diff --git a/client/transports/Network/network.c b/client/transports/Network/network.c index 4926796e..4f653951 100644 --- a/client/transports/Network/network.c +++ b/client/transports/Network/network.c @@ -136,9 +136,9 @@ static void networkSetup(void) { .module = "network", .name = "password", - .description = - "Reserved network password (empty uses no authentication)", + .description = "Network password (empty uses no authentication)", .type = OPTION_TYPE_STRING, + .sensitive = true, .value.x_string = NULL, }, { diff --git a/client/transports/Network/platform/linux/auth_crypto.c b/client/transports/Network/platform/linux/auth_crypto.c new file mode 100644 index 00000000..091818b3 --- /dev/null +++ b/client/transports/Network/platform/linux/auth_crypto.c @@ -0,0 +1,112 @@ +/** + * 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 "../../auth_crypto.h" + +#include +#include +#include +#include + +#include + +static const uint8_t EMPTY_INPUT = 0; + +static const uint8_t * inputBytes(const void * data, size_t size) +{ + return size ? (const uint8_t *)data : &EMPTY_INPUT; +} + +bool networkAuthRandomBytes(void * data, size_t size) +{ + if (size && !data) + return false; + + uint8_t * output = (uint8_t *)data; + const size_t totalSize = size; + while (size) + { + const int chunk = size > INT_MAX ? INT_MAX : (int)size; + if (RAND_bytes(output, chunk) != 1) + { + networkAuthSecureZero(data, totalSize); + return false; + } + + output += chunk; + size -= (size_t)chunk; + } + + return true; +} + +bool networkAuthPBKDF2HMACSHA256(const void * password, + size_t passwordSize, const void * salt, size_t saltSize, + uint32_t iterations, void * key, size_t keySize) +{ + if ((passwordSize && !password) || (saltSize && !salt) || !iterations || + !key || !keySize || passwordSize > INT_MAX || saltSize > INT_MAX || + keySize > INT_MAX || iterations > INT_MAX) + return false; + + const int result = PKCS5_PBKDF2_HMAC( + (const char *)inputBytes(password, passwordSize), (int)passwordSize, + inputBytes(salt, saltSize), (int)saltSize, (int)iterations, + EVP_sha256(), (int)keySize, (uint8_t *)key); + if (result == 1) + return true; + + networkAuthSecureZero(key, keySize); + return false; +} + +bool networkAuthHMACSHA256(const void * key, size_t keySize, + const void * data, size_t size, + uint8_t output[NETWORK_AUTH_SHA256_SIZE]) +{ + if ((keySize && !key) || (size && !data) || keySize > INT_MAX || !output) + return false; + + unsigned int outputSize = 0; + if (HMAC(EVP_sha256(), inputBytes(key, keySize), (int)keySize, + inputBytes(data, size), size, output, &outputSize) && + outputSize == NETWORK_AUTH_SHA256_SIZE) + return true; + + networkAuthSecureZero(output, NETWORK_AUTH_SHA256_SIZE); + return false; +} + +bool networkAuthConstantTimeEqual( + const void * left, const void * right, size_t size) +{ + if (size && (!left || !right)) + return false; + if (!size) + return true; + + return CRYPTO_memcmp(left, right, size) == 0; +} + +void networkAuthSecureZero(void * data, size_t size) +{ + if (data && size) + OPENSSL_cleanse(data, size); +} diff --git a/client/transports/Network/platform/windows/auth_crypto.c b/client/transports/Network/platform/windows/auth_crypto.c new file mode 100644 index 00000000..df464935 --- /dev/null +++ b/client/transports/Network/platform/windows/auth_crypto.c @@ -0,0 +1,166 @@ +/** + * 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 "../../auth_crypto.h" + +#include +#include + +#include + +static const uint8_t EMPTY_INPUT = 0; + +static PUCHAR inputBytes(const void * data, size_t size) +{ + return (PUCHAR)(size ? data : &EMPTY_INPUT); +} + +static bool openHMACSHA256(BCRYPT_ALG_HANDLE * algorithm) +{ + return algorithm && BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(algorithm, + BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG)); +} + +bool networkAuthRandomBytes(void * data, size_t size) +{ + if (size && !data) + return false; + + uint8_t * output = (uint8_t *)data; + const size_t totalSize = size; + while (size) + { + const ULONG chunk = size > ULONG_MAX ? ULONG_MAX : (ULONG)size; + if (!BCRYPT_SUCCESS(BCryptGenRandom(NULL, output, chunk, + BCRYPT_USE_SYSTEM_PREFERRED_RNG))) + { + networkAuthSecureZero(data, totalSize); + return false; + } + + output += chunk; + size -= chunk; + } + + return true; +} + +bool networkAuthPBKDF2HMACSHA256(const void * password, + size_t passwordSize, const void * salt, size_t saltSize, + uint32_t iterations, void * key, size_t keySize) +{ + 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 = NULL; + if (!openHMACSHA256(&algorithm)) + { + networkAuthSecureZero(key, keySize); + return false; + } + + const NTSTATUS status = BCryptDeriveKeyPBKDF2(algorithm, + inputBytes(password, passwordSize), (ULONG)passwordSize, + inputBytes(salt, saltSize), (ULONG)saltSize, iterations, + (PUCHAR)key, (ULONG)keySize, 0); + BCryptCloseAlgorithmProvider(algorithm, 0); + if (BCRYPT_SUCCESS(status)) + return true; + + networkAuthSecureZero(key, keySize); + return false; +} + +bool networkAuthHMACSHA256(const void * key, size_t keySize, + const void * data, size_t size, + uint8_t output[NETWORK_AUTH_SHA256_SIZE]) +{ + if ((keySize && !key) || (size && !data) || keySize > INT_MAX || !output) + return false; + + bool result = false; + BCRYPT_ALG_HANDLE algorithm = NULL; + BCRYPT_HASH_HANDLE hash = NULL; + PUCHAR hashObject = NULL; + ULONG hashSize = 0; + ULONG propertySize = 0; + const uint8_t * input = (const uint8_t *)data; + + if (!openHMACSHA256(&algorithm) || + !BCRYPT_SUCCESS(BCryptGetProperty(algorithm, BCRYPT_OBJECT_LENGTH, + (PUCHAR)&hashSize, sizeof(hashSize), &propertySize, 0)) || + propertySize != sizeof(hashSize) || !hashSize) + goto cleanup; + + hashObject = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, hashSize); + if (!hashObject || + !BCRYPT_SUCCESS(BCryptCreateHash(algorithm, &hash, hashObject, + hashSize, inputBytes(key, keySize), (ULONG)keySize, 0))) + goto cleanup; + + while (size) + { + const ULONG chunk = size > ULONG_MAX ? ULONG_MAX : (ULONG)size; + if (!BCRYPT_SUCCESS(BCryptHashData(hash, (PUCHAR)input, chunk, 0))) + goto cleanup; + + input += chunk; + size -= chunk; + } + + result = BCRYPT_SUCCESS(BCryptFinishHash( + hash, output, NETWORK_AUTH_SHA256_SIZE, 0)); + +cleanup: + if (hash) + BCryptDestroyHash(hash); + if (hashObject) + { + SecureZeroMemory(hashObject, hashSize); + HeapFree(GetProcessHeap(), 0, hashObject); + } + if (algorithm) + BCryptCloseAlgorithmProvider(algorithm, 0); + if (!result) + networkAuthSecureZero(output, NETWORK_AUTH_SHA256_SIZE); + return result; +} + +bool networkAuthConstantTimeEqual( + const void * left, const void * right, size_t size) +{ + if (size && (!left || !right)) + return false; + + const volatile uint8_t * leftBytes = (const volatile uint8_t *)left; + const volatile uint8_t * rightBytes = (const volatile uint8_t *)right; + volatile uint8_t difference = 0; + for (size_t index = 0; index < size; ++index) + difference |= leftBytes[index] ^ rightBytes[index]; + return difference == 0; +} + +void networkAuthSecureZero(void * data, size_t size) +{ + if (data && size) + SecureZeroMemory(data, size); +} diff --git a/common/include/common/option.h b/common/include/common/option.h index 9132e883..547c1a9a 100644 --- a/common/include/common/option.h +++ b/common/include/common/option.h @@ -54,8 +54,9 @@ struct Option char * old_name; char * description; - char shortopt; - bool preset; + char shortopt; + bool preset; + bool sensitive; void * opaque; enum OptionType type; diff --git a/common/src/option.c b/common/src/option.c index 185b8272..f1df1285 100644 --- a/common/src/option.c +++ b/common/src/option.c @@ -78,10 +78,26 @@ static bool float_parser(struct Option * opt, const char * str) return true; } +static void string_free(char * value) +{ + if (!value) + return; + + const size_t length = strlen(value); + volatile unsigned char * bytes = (volatile unsigned char *)value; + for (size_t i = 0; i < length; ++i) + bytes[i] = 0; + free(value); +} + static bool string_parser(struct Option * opt, const char * str) { - free(opt->value.x_string); - opt->value.x_string = lg_strdup(str); + char * value = lg_strdup(str); + if (str && !value) + return false; + + string_free(opt->value.x_string); + opt->value.x_string = value; return true; } @@ -286,8 +302,8 @@ void option_free(void) if (o->cleanup) o->cleanup(o); - if (o->type == OPTION_TYPE_STRING && o->value.x_string) - free(o->value.x_string); + if (o->type == OPTION_TYPE_STRING) + string_free(o->value.x_string); free(o); } free(state.options); @@ -382,7 +398,7 @@ bool option_parse(int argc, char * argv[]) if (!module || !name) { DEBUG_WARN("Ignored invalid argument: %s", argv[a]); - free(arg); + string_free(arg); continue; } @@ -404,13 +420,13 @@ bool option_parse(int argc, char * argv[]) value = v; } - free(arg); + string_free(arg); } if (!o) { DEBUG_WARN("Ignored unknown argument: %s", argv[a]); - free(value); + string_free(value); continue; } @@ -429,7 +445,7 @@ bool option_parse(int argc, char * argv[]) } option_set(o, value); - free(value); + string_free(value); } return true; @@ -592,7 +608,7 @@ bool option_load(const char * filename) free(name); name = NULL; nameLen = 0; - free(value); + string_free(value); value = NULL; valueLen = 0; break; @@ -681,7 +697,7 @@ exit: free(module); free(name ); - free(value ); + string_free(value); return result; } @@ -781,7 +797,9 @@ void option_print(void) struct Option * o = state.groups[g].options[i]; if (o->preset) continue; - char * value = o->toString(o); + char * value = o->sensitive && o->type == OPTION_TYPE_STRING && + o->value.x_string && *o->value.x_string ? + lg_strdup("") : o->toString(o); if (!value) { value = lg_strdup("NULL"); @@ -894,7 +912,7 @@ bool option_dump_preset(FILE * file) char * value = o->toString(o); fprintf(file, "%s=%s\n", o->name, value); - free(value); + string_free(value); } fputc('\n', file); } @@ -995,8 +1013,15 @@ void option_set_string(const char * module, const char * name, const char * valu return; } DEBUG_ASSERT(o->type == OPTION_TYPE_STRING); - free(o->value.x_string); - o->value.x_string = lg_strdup(value); + char * replacement = lg_strdup(value); + if (value && !replacement) + { + DEBUG_ERROR("out of memory"); + return; + } + + string_free(o->value.x_string); + o->value.x_string = replacement; } void option_set_bool(const char * module, const char * name, bool value) diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj index f73fb647..a2d00af3 100644 --- a/idd/LGCommon/LGCommon.vcxproj +++ b/idd/LGCommon/LGCommon.vcxproj @@ -69,6 +69,7 @@ + diff --git a/idd/LGCommon/LGCommon.vcxproj.filters b/idd/LGCommon/LGCommon.vcxproj.filters index e737b746..f3f048f1 100644 --- a/idd/LGCommon/LGCommon.vcxproj.filters +++ b/idd/LGCommon/LGCommon.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files + Source Files diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index ba8a97a5..4a9f122c 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -91,6 +91,7 @@ + @@ -175,6 +176,7 @@ + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index fe3cde3c..1a681649 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -295,6 +295,9 @@ Transport\Network + + Transport\Network + Transport\Network @@ -483,6 +486,9 @@ Transport\Network + + Transport\Network + Transport\Network diff --git a/idd/LGIdd/transport/network/CNetworkServer.cpp b/idd/LGIdd/transport/network/CNetworkServer.cpp index 6efffe23..b255593e 100644 --- a/idd/LGIdd/transport/network/CNetworkServer.cpp +++ b/idd/LGIdd/transport/network/CNetworkServer.cpp @@ -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 #include #include #include @@ -34,7 +36,6 @@ #include #include -#include #include #include @@ -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(data), - static_cast(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(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& password) + { + if (source.empty() || source.size() > INT_MAX) + return false; + + const int length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + source.data(), static_cast(source.size()), nullptr, 0, + nullptr, nullptr); + if (length <= 0) + return false; + + try + { + password.resize(static_cast(length)); + } + catch (...) + { + return false; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + source.data(), static_cast(source.size()), + reinterpret_cast(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(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 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 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(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(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 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(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 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 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(); +} diff --git a/idd/LGIdd/transport/network/CNetworkServer.h b/idd/LGIdd/transport/network/CNetworkServer.h index 70036f39..10b97960 100644 --- a/idd/LGIdd/transport/network/CNetworkServer.h +++ b/idd/LGIdd/transport/network/CNetworkServer.h @@ -41,4 +41,5 @@ public: bool Start(); void Stop(); + void Process(); }; diff --git a/idd/LGIdd/transport/network/CNetworkTransport.cpp b/idd/LGIdd/transport/network/CNetworkTransport.cpp index 27a2c274..57ae43d6 100644 --- a/idd/LGIdd/transport/network/CNetworkTransport.cpp +++ b/idd/LGIdd/transport/network/CNetworkTransport.cpp @@ -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) { diff --git a/idd/LGIdd/transport/network/NetworkAuthCrypto.cpp b/idd/LGIdd/transport/network/NetworkAuthCrypto.cpp new file mode 100644 index 00000000..e138d3cb --- /dev/null +++ b/idd/LGIdd/transport/network/NetworkAuthCrypto.cpp @@ -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 +#include + +#include + +namespace +{ + static const std::uint8_t EMPTY_INPUT = 0; + + PUCHAR InputBytes(const void * data, std::size_t size) noexcept + { + return const_cast(static_cast( + 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(data); + const std::size_t totalSize = size; + while (size) + { + const ULONG chunk = size > ULONG_MAX ? ULONG_MAX : + static_cast(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(passwordSize), + InputBytes(salt, saltSize), static_cast(saltSize), iterations, + static_cast(key), static_cast(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(data); + + if (!OpenHMACSHA256(algorithm) || + !BCRYPT_SUCCESS(BCryptGetProperty(algorithm, BCRYPT_OBJECT_LENGTH, + reinterpret_cast(&hashSize), sizeof(hashSize), + &propertySize, 0)) || + propertySize != sizeof(hashSize) || !hashSize) + goto cleanup; + + hashObject = static_cast( + HeapAlloc(GetProcessHeap(), 0, hashSize)); + if (!hashObject || + !BCRYPT_SUCCESS(BCryptCreateHash(algorithm, &hash, hashObject, + hashSize, InputBytes(key, keySize), static_cast(keySize), 0))) + goto cleanup; + + while (size) + { + const ULONG chunk = size > ULONG_MAX ? ULONG_MAX : + static_cast(size); + if (!BCRYPT_SUCCESS(BCryptHashData( + hash, const_cast(input), chunk, 0))) + goto cleanup; + + input += chunk; + size -= chunk; + } + + result = BCRYPT_SUCCESS(BCryptFinishHash( + hash, output, static_cast(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(left); + const volatile std::uint8_t * rightBytes = + static_cast(right); + volatile std::uint8_t difference = 0; + for (std::size_t index = 0; index < size; ++index) + { + difference = static_cast(difference | + static_cast(leftBytes[index] ^ rightBytes[index])); + } + return difference == 0; + } + + void SecureZero(void * data, std::size_t size) noexcept + { + if (data && size) + SecureZeroMemory(data, size); + } +} diff --git a/idd/LGIdd/transport/network/NetworkAuthCrypto.h b/idd/LGIdd/transport/network/NetworkAuthCrypto.h new file mode 100644 index 00000000..9a9e862e --- /dev/null +++ b/idd/LGIdd/transport/network/NetworkAuthCrypto.h @@ -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 +#include + +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; +}