fix: Key validation failure
This commit is contained in:
@@ -23,6 +23,8 @@ SecureCipher::SecureCipher()
|
|||||||
sodium_memzero(m_sk, sizeof(m_sk));
|
sodium_memzero(m_sk, sizeof(m_sk));
|
||||||
sodium_memzero(m_tx_key, sizeof(m_tx_key));
|
sodium_memzero(m_tx_key, sizeof(m_tx_key));
|
||||||
sodium_memzero(m_rx_key, sizeof(m_rx_key));
|
sodium_memzero(m_rx_key, sizeof(m_rx_key));
|
||||||
|
sodium_memzero(m_tx_stream_nonce, sizeof(m_tx_stream_nonce));
|
||||||
|
sodium_memzero(m_rx_stream_nonce, sizeof(m_rx_stream_nonce));
|
||||||
sodium_memzero(m_session_token, sizeof(m_session_token));
|
sodium_memzero(m_session_token, sizeof(m_session_token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ void SecureCipher::CleanUp()
|
|||||||
sodium_memzero(m_sk, sizeof(m_sk));
|
sodium_memzero(m_sk, sizeof(m_sk));
|
||||||
sodium_memzero(m_tx_key, sizeof(m_tx_key));
|
sodium_memzero(m_tx_key, sizeof(m_tx_key));
|
||||||
sodium_memzero(m_rx_key, sizeof(m_rx_key));
|
sodium_memzero(m_rx_key, sizeof(m_rx_key));
|
||||||
|
sodium_memzero(m_tx_stream_nonce, sizeof(m_tx_stream_nonce));
|
||||||
|
sodium_memzero(m_rx_stream_nonce, sizeof(m_rx_stream_nonce));
|
||||||
sodium_memzero(m_session_token, sizeof(m_session_token));
|
sodium_memzero(m_session_token, sizeof(m_session_token));
|
||||||
|
|
||||||
m_initialized = false;
|
m_initialized = false;
|
||||||
@@ -84,6 +88,13 @@ bool SecureCipher::ComputeClientKeys(const uint8_t* server_pk)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up fixed stream nonces per direction
|
||||||
|
// client→server = 0x02, server→client = 0x01
|
||||||
|
sodium_memzero(m_tx_stream_nonce, NONCE_SIZE);
|
||||||
|
m_tx_stream_nonce[0] = 0x02;
|
||||||
|
sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
|
||||||
|
m_rx_stream_nonce[0] = 0x01;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +111,13 @@ bool SecureCipher::ComputeServerKeys(const uint8_t* client_pk)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up fixed stream nonces per direction
|
||||||
|
// server→client = 0x01, client→server = 0x02
|
||||||
|
sodium_memzero(m_tx_stream_nonce, NONCE_SIZE);
|
||||||
|
m_tx_stream_nonce[0] = 0x01;
|
||||||
|
sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
|
||||||
|
m_rx_stream_nonce[0] = 0x02;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +128,9 @@ void SecureCipher::GenerateChallenge(uint8_t* out_challenge)
|
|||||||
|
|
||||||
void SecureCipher::ComputeChallengeResponse(const uint8_t* challenge, uint8_t* out_response)
|
void SecureCipher::ComputeChallengeResponse(const uint8_t* challenge, uint8_t* out_response)
|
||||||
{
|
{
|
||||||
// HMAC the challenge using our rx_key as the authentication key
|
// HMAC the challenge using our tx_key as the authentication key
|
||||||
// This proves we derived the correct shared secret
|
// Client tx_key == Server rx_key, so the server can verify with its rx_key
|
||||||
crypto_auth(out_response, challenge, CHALLENGE_SIZE, m_rx_key);
|
crypto_auth(out_response, challenge, CHALLENGE_SIZE, m_tx_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SecureCipher::VerifyChallengeResponse(const uint8_t* challenge, const uint8_t* response)
|
bool SecureCipher::VerifyChallengeResponse(const uint8_t* challenge, const uint8_t* response)
|
||||||
@@ -121,21 +139,36 @@ bool SecureCipher::VerifyChallengeResponse(const uint8_t* challenge, const uint8
|
|||||||
return crypto_auth_verify(response, challenge, CHALLENGE_SIZE, m_rx_key) == 0;
|
return crypto_auth_verify(response, challenge, CHALLENGE_SIZE, m_rx_key) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureCipher::BuildNonce(uint8_t* nonce, uint64_t counter, bool is_tx)
|
void SecureCipher::ApplyStreamCipher(void* buffer, size_t len,
|
||||||
|
const uint8_t* key, uint64_t& byte_counter,
|
||||||
|
const uint8_t* stream_nonce)
|
||||||
{
|
{
|
||||||
// 24-byte nonce structure:
|
uint8_t* p = (uint8_t*)buffer;
|
||||||
// [0]: direction flag (0x01 for tx, 0x02 for rx)
|
|
||||||
// [1-7]: reserved/zero
|
|
||||||
// [8-15]: 64-bit counter (little-endian)
|
|
||||||
// [16-23]: reserved/zero
|
|
||||||
|
|
||||||
sodium_memzero(nonce, NONCE_SIZE);
|
// Handle partial leading block (if byte_counter isn't block-aligned)
|
||||||
nonce[0] = is_tx ? 0x01 : 0x02;
|
uint32_t offset = (uint32_t)(byte_counter % 64);
|
||||||
|
if (offset != 0 && len > 0)
|
||||||
// Store counter in little-endian at offset 8
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
{
|
||||||
nonce[8 + i] = (uint8_t)(counter >> (i * 8));
|
// Generate full keystream block, use only the portion we need
|
||||||
|
uint8_t ks[64];
|
||||||
|
sodium_memzero(ks, 64);
|
||||||
|
crypto_stream_xchacha20_xor_ic(ks, ks, 64, stream_nonce, byte_counter / 64, key);
|
||||||
|
|
||||||
|
size_t use = len < (64 - offset) ? len : (64 - offset);
|
||||||
|
for (size_t i = 0; i < use; ++i)
|
||||||
|
p[i] ^= ks[offset + i];
|
||||||
|
|
||||||
|
p += use;
|
||||||
|
len -= use;
|
||||||
|
byte_counter += use;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle remaining data (starts at a block boundary)
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
crypto_stream_xchacha20_xor_ic(p, p, (unsigned long long)len,
|
||||||
|
stream_nonce, byte_counter / 64, key);
|
||||||
|
byte_counter += len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,23 +179,23 @@ size_t SecureCipher::Encrypt(const void* plaintext, size_t plaintext_len, void*
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AEAD encryption uses a random nonce (not the stream nonce)
|
||||||
uint8_t nonce[NONCE_SIZE];
|
uint8_t nonce[NONCE_SIZE];
|
||||||
BuildNonce(nonce, m_tx_nonce, true);
|
randombytes_buf(nonce, NONCE_SIZE);
|
||||||
|
|
||||||
unsigned long long ciphertext_len = 0;
|
unsigned long long ciphertext_len = 0;
|
||||||
|
|
||||||
if (crypto_aead_xchacha20poly1305_ietf_encrypt(
|
if (crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||||
(uint8_t*)ciphertext, &ciphertext_len,
|
(uint8_t*)ciphertext, &ciphertext_len,
|
||||||
(const uint8_t*)plaintext, plaintext_len,
|
(const uint8_t*)plaintext, plaintext_len,
|
||||||
nullptr, 0, // No additional data
|
nullptr, 0,
|
||||||
nullptr, // No secret nonce
|
nullptr,
|
||||||
nonce,
|
nonce,
|
||||||
m_tx_key) != 0)
|
m_tx_key) != 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
++m_tx_nonce;
|
|
||||||
return (size_t)ciphertext_len;
|
return (size_t)ciphertext_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,23 +212,21 @@ size_t SecureCipher::Decrypt(const void* ciphertext, size_t ciphertext_len, void
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t nonce[NONCE_SIZE];
|
uint8_t nonce[NONCE_SIZE];
|
||||||
BuildNonce(nonce, m_rx_nonce, false);
|
randombytes_buf(nonce, NONCE_SIZE);
|
||||||
|
|
||||||
unsigned long long plaintext_len = 0;
|
unsigned long long plaintext_len = 0;
|
||||||
|
|
||||||
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
|
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
(uint8_t*)plaintext, &plaintext_len,
|
(uint8_t*)plaintext, &plaintext_len,
|
||||||
nullptr, // No secret nonce output
|
nullptr,
|
||||||
(const uint8_t*)ciphertext, ciphertext_len,
|
(const uint8_t*)ciphertext, ciphertext_len,
|
||||||
nullptr, 0, // No additional data
|
nullptr, 0,
|
||||||
nonce,
|
nonce,
|
||||||
m_rx_key) != 0)
|
m_rx_key) != 0)
|
||||||
{
|
{
|
||||||
// Decryption failed - either wrong key, tampered data, or replay attack
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
++m_rx_nonce;
|
|
||||||
return (size_t)plaintext_len;
|
return (size_t)plaintext_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,18 +235,7 @@ void SecureCipher::EncryptInPlace(void* buffer, size_t len)
|
|||||||
if (!m_activated || len == 0)
|
if (!m_activated || len == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint8_t nonce[NONCE_SIZE];
|
ApplyStreamCipher(buffer, len, m_tx_key, m_tx_nonce, m_tx_stream_nonce);
|
||||||
BuildNonce(nonce, m_tx_nonce, true);
|
|
||||||
|
|
||||||
crypto_stream_xchacha20_xor_ic(
|
|
||||||
(uint8_t*)buffer,
|
|
||||||
(const uint8_t*)buffer,
|
|
||||||
(unsigned long long)len,
|
|
||||||
nonce,
|
|
||||||
0,
|
|
||||||
m_tx_key);
|
|
||||||
|
|
||||||
++m_tx_nonce;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureCipher::DecryptInPlace(void* buffer, size_t len)
|
void SecureCipher::DecryptInPlace(void* buffer, size_t len)
|
||||||
@@ -223,18 +243,7 @@ void SecureCipher::DecryptInPlace(void* buffer, size_t len)
|
|||||||
if (!m_activated || len == 0)
|
if (!m_activated || len == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint8_t nonce[NONCE_SIZE];
|
ApplyStreamCipher(buffer, len, m_rx_key, m_rx_nonce, m_rx_stream_nonce);
|
||||||
BuildNonce(nonce, m_rx_nonce, false);
|
|
||||||
|
|
||||||
crypto_stream_xchacha20_xor_ic(
|
|
||||||
(uint8_t*)buffer,
|
|
||||||
(const uint8_t*)buffer,
|
|
||||||
(unsigned long long)len,
|
|
||||||
nonce,
|
|
||||||
0,
|
|
||||||
m_rx_key);
|
|
||||||
|
|
||||||
++m_rx_nonce;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SecureCipher::EncryptToken(const uint8_t* plaintext, size_t len,
|
bool SecureCipher::EncryptToken(const uint8_t* plaintext, size_t len,
|
||||||
|
|||||||
@@ -90,13 +90,18 @@ private:
|
|||||||
uint8_t m_tx_key[KEY_SIZE]; // Key for encrypting outgoing packets
|
uint8_t m_tx_key[KEY_SIZE]; // Key for encrypting outgoing packets
|
||||||
uint8_t m_rx_key[KEY_SIZE]; // Key for decrypting incoming packets
|
uint8_t m_rx_key[KEY_SIZE]; // Key for decrypting incoming packets
|
||||||
|
|
||||||
// Nonce counters - prevent replay attacks
|
// Byte counters for continuous stream cipher
|
||||||
uint64_t m_tx_nonce = 0;
|
uint64_t m_tx_nonce = 0;
|
||||||
uint64_t m_rx_nonce = 0;
|
uint64_t m_rx_nonce = 0;
|
||||||
|
|
||||||
|
// Fixed nonces per direction (set during key exchange)
|
||||||
|
uint8_t m_tx_stream_nonce[NONCE_SIZE];
|
||||||
|
uint8_t m_rx_stream_nonce[NONCE_SIZE];
|
||||||
|
|
||||||
// Server-generated session token
|
// Server-generated session token
|
||||||
uint8_t m_session_token[SESSION_TOKEN_SIZE];
|
uint8_t m_session_token[SESSION_TOKEN_SIZE];
|
||||||
|
|
||||||
// Build 24-byte nonce from counter
|
// Continuous stream cipher operation (handles arbitrary chunk sizes)
|
||||||
void BuildNonce(uint8_t* nonce, uint64_t counter, bool is_tx);
|
void ApplyStreamCipher(void* buffer, size_t len, const uint8_t* key,
|
||||||
|
uint64_t& byte_counter, const uint8_t* stream_nonce);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ bool CNetworkStream::IsSecurityMode()
|
|||||||
return m_secureCipher.IsActivated();
|
return m_secureCipher.IsActivated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CNetworkStream::DecryptPendingRecvData()
|
||||||
|
{
|
||||||
|
int remaining = m_recvBufInputPos - m_recvBufOutputPos;
|
||||||
|
if (remaining > 0 && m_secureCipher.IsActivated())
|
||||||
|
{
|
||||||
|
m_secureCipher.DecryptInPlace(m_recvBuf + m_recvBufOutputPos, remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CNetworkStream::SetRecvBufferSize(int recvBufSize)
|
void CNetworkStream::SetRecvBufferSize(int recvBufSize)
|
||||||
{
|
{
|
||||||
if (m_recvBuf)
|
if (m_recvBuf)
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ class CNetworkStream
|
|||||||
bool IsSecureCipherActivated() const { return m_secureCipher.IsActivated(); }
|
bool IsSecureCipherActivated() const { return m_secureCipher.IsActivated(); }
|
||||||
void ActivateSecureCipher() { m_secureCipher.SetActivated(true); }
|
void ActivateSecureCipher() { m_secureCipher.SetActivated(true); }
|
||||||
|
|
||||||
|
// Decrypt any unprocessed data already in the recv buffer
|
||||||
|
// Must be called after activating the cipher mid-stream
|
||||||
|
void DecryptPendingRecvData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
time_t m_connectLimitTime;
|
time_t m_connectLimitTime;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ void Traceback()
|
|||||||
str.append("\n");
|
str.append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!PyErr_Occurred())
|
||||||
|
{
|
||||||
|
str.append("(No Python error set - failure occurred at C++ level)");
|
||||||
|
LogBoxf("Traceback:\n\n%s\n", str.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject * exc;
|
PyObject * exc;
|
||||||
PyObject * v;
|
PyObject * v;
|
||||||
PyObject * tb;
|
PyObject * tb;
|
||||||
@@ -44,6 +51,42 @@ void Traceback()
|
|||||||
PyErr_Fetch(&exc, &v, &tb);
|
PyErr_Fetch(&exc, &v, &tb);
|
||||||
PyErr_NormalizeException(&exc, &v, &tb);
|
PyErr_NormalizeException(&exc, &v, &tb);
|
||||||
|
|
||||||
|
// Try using traceback.format_exception for full details
|
||||||
|
PyObject* tbMod = PyImport_ImportModule("traceback");
|
||||||
|
if (tbMod)
|
||||||
|
{
|
||||||
|
PyObject* fmtFunc = PyObject_GetAttrString(tbMod, "format_exception");
|
||||||
|
if (fmtFunc)
|
||||||
|
{
|
||||||
|
PyObject* result = PyObject_CallFunction(fmtFunc, (char*)"OOO",
|
||||||
|
exc ? exc : Py_None,
|
||||||
|
v ? v : Py_None,
|
||||||
|
tb ? tb : Py_None);
|
||||||
|
if (result && PyList_Check(result))
|
||||||
|
{
|
||||||
|
Py_ssize_t n = PyList_Size(result);
|
||||||
|
for (Py_ssize_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
PyObject* line = PyList_GetItem(result, i);
|
||||||
|
if (line && PyString_Check(line))
|
||||||
|
str.append(PyString_AS_STRING(line));
|
||||||
|
}
|
||||||
|
Py_DECREF(result);
|
||||||
|
Py_DECREF(fmtFunc);
|
||||||
|
Py_DECREF(tbMod);
|
||||||
|
Py_XDECREF(exc);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
Py_XDECREF(tb);
|
||||||
|
LogBoxf("Traceback:\n\n%s\n", str.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_XDECREF(result);
|
||||||
|
Py_DECREF(fmtFunc);
|
||||||
|
}
|
||||||
|
Py_DECREF(tbMod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: manual extraction
|
||||||
if (exc)
|
if (exc)
|
||||||
{
|
{
|
||||||
PyObject* excName = PyObject_GetAttrString(exc, "__name__");
|
PyObject* excName = PyObject_GetAttrString(exc, "__name__");
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ bool CAccountConnector::__AuthState_RecvKeyComplete()
|
|||||||
|
|
||||||
cipher.SetSessionToken(session_token);
|
cipher.SetSessionToken(session_token);
|
||||||
cipher.SetActivated(true);
|
cipher.SetActivated(true);
|
||||||
|
DecryptPendingRecvData();
|
||||||
|
|
||||||
Tracen("Secure channel established - encryption activated");
|
Tracen("Secure channel established - encryption activated");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -470,6 +470,7 @@ bool CGuildMarkDownloader::__LoginState_RecvKeyComplete()
|
|||||||
|
|
||||||
cipher.SetSessionToken(session_token);
|
cipher.SetSessionToken(session_token);
|
||||||
cipher.SetActivated(true);
|
cipher.SetActivated(true);
|
||||||
|
DecryptPendingRecvData();
|
||||||
|
|
||||||
Tracen("SECURE CIPHER ACTIVATED");
|
Tracen("SECURE CIPHER ACTIVATED");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -430,6 +430,7 @@ bool CGuildMarkUploader::__LoginState_RecvKeyComplete()
|
|||||||
|
|
||||||
cipher.SetSessionToken(session_token);
|
cipher.SetSessionToken(session_token);
|
||||||
cipher.SetActivated(true);
|
cipher.SetActivated(true);
|
||||||
|
DecryptPendingRecvData();
|
||||||
|
|
||||||
Tracen("SECURE CIPHER ACTIVATED");
|
Tracen("SECURE CIPHER ACTIVATED");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ bool CPythonNetworkStream::RecvKeyComplete()
|
|||||||
|
|
||||||
cipher.SetSessionToken(decrypted_token);
|
cipher.SetSessionToken(decrypted_token);
|
||||||
cipher.SetActivated(true);
|
cipher.SetActivated(true);
|
||||||
|
DecryptPendingRecvData();
|
||||||
|
|
||||||
Tracen("SECURE CIPHER ACTIVATED");
|
Tracen("SECURE CIPHER ACTIVATED");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user