mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +00:00
[client/idd] network: add password authentication
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
38
client/transports/Network/auth_crypto.h
Normal file
38
client/transports/Network/auth_crypto.h
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
112
client/transports/Network/platform/linux/auth_crypto.c
Normal file
112
client/transports/Network/platform/linux/auth_crypto.c
Normal 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);
|
||||
}
|
||||
166
client/transports/Network/platform/windows/auth_crypto.c
Normal file
166
client/transports/Network/platform/windows/auth_crypto.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user