[client/idd] network: add password authentication

This commit is contained in:
Geoffrey McRae
2026-09-30 19:56:54 +10:00
parent 5138f470b7
commit 6dfba7519c
17 changed files with 1546 additions and 146 deletions

View File

@@ -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
)

View File

@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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);

View File

@@ -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 <LGProtocol/NetworkAuth.h>
#include <LGProtocol/NetworkPacket.h>
#include <LGProtocol/NetworkRoute.h>
#include <LGProtocol/NetworkSequence.h>
@@ -41,18 +43,26 @@
#include <stdlib.h>
#include <string.h>
#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;

View File

@@ -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,
},
{

View File

@@ -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 <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <limits.h>
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);
}

View File

@@ -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 <windows.h>
#include <bcrypt.h>
#include <limits.h>
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);
}