Loading src/event/iocp.cpp +101 −101 Original line number Diff line number Diff line Loading @@ -93,63 +93,55 @@ namespace netplus { class EventWorker { public: /** * @brief Helper to initiate a raw overlapped read. * This fills the context buffer which recvDataWSA will later process. */ static void start_read(client* ctx) { con& c = *ctx->CurCon; // Point the WSA buffer to our persistent context buffer // Point the Overlapped buffer to our persistent context memory buffer buf(ctx->readCtx.buffer, BLOCKSIZE); if (c.csock->_Type == sockettype::TCP) { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); } else if (c.csock->_Type == sockettype::SSL) { // Note: For SSL, this initiates the raw TCP read into _rx_netbuf // This triggers the raw WSARecv. For SSL, it fills the internal _rx_netbuf. if (c.csock->_Type == sockettype::SSL) { static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, 0); } else { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); } } /** * @brief Helper to initiate a raw overlapped write. */ static void start_write(client* ctx) { con& c = *ctx->CurCon; if (c.SendData.empty()) return; // Take plaintext from SendData and pass it to the WSA method // For SSL, this will perform the encryption before calling WSASend size_t toSend = (std::min)((size_t)BLOCKSIZE, c.SendData.size()); buffer out(c.SendData.data(), toSend); if (c.csock->_Type == sockettype::TCP) { static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); } else if (c.csock->_Type == sockettype::SSL) { // This triggers SSL encryption then calls internal WSASend static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); size_t consumed = 0; if (c.csock->_Type == sockettype::SSL) { consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); } else { consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); } // Remove the plaintext from the queue that was actually "consumed" into a TLS record if (consumed > 0) c.SendData.erase(c.SendData.begin(), c.SendData.begin() + consumed); } /** * @brief The core IOCP Worker Loop */ EventWorker(int tid, ULONG_PTR args, EventWorkerArgs* eargs) { while (event::Running) { DWORD dwBytesTransfered = 0; ULONG_PTR lpContext = 0; OVERLAPPED* pOverlapped = nullptr; // 1. Wait for completion notification from Windows Kernel BOOL bReturn = GetQueuedCompletionStatus(eargs->eviocp, &dwBytesTransfered, &lpContext, &pOverlapped, eargs->timeout); // Handle timeout (eargs->timeout) if (lpContext == 0 && GetLastError() == WAIT_TIMEOUT) continue; if (lpContext == 0) continue; client* pClientContext = reinterpret_cast<client*>(lpContext); IO_CONTEXT* pIoCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, overlapped); con& c = *pClientContext->CurCon; // 2. Handle Disconnection or Socket Errors if (!bReturn || (bReturn && dwBytesTransfered == 0)) { eargs->event->DisconnectEvent(c, tid, (ULONG_PTR)eargs->args); delete pClientContext; Loading @@ -163,33 +155,42 @@ namespace netplus { if (c.csock->_Type == sockettype::SSL) { ssl* sslSocket = static_cast<ssl*>(c.csock.get()); sslSocket->_rx_netbuf.insert( sslSocket->_rx_netbuf.end(), // 1. Move raw ciphertext from IOCP buffer to SSL's rx_netbuf sslSocket->_rx_netbuf.insert(sslSocket->_rx_netbuf.end(), pIoCtx->buffer, pIoCtx->buffer + dwBytesTransfered ); pIoCtx->buffer + dwBytesTransfered); // 2. Process all complete TLS records currently in the buffer buffer plain(BLOCKSIZE); size_t decrypted = sslSocket->recvDataWSA(plain, 0); if (decrypted > 0) { size_t decrypted = 0; while ((decrypted = sslSocket->recvDataWSA(plain, 0)) > 0) { c.RecvData.append(plain.data.buf, decrypted); eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); } } else { // Standard TCP: Raw data is the application data // Plain TCP c.RecvData.append(pIoCtx->buffer, dwBytesTransfered); eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); } // Determine next step: Write if data pending, else keep reading // Continue the loop if (!c.SendData.empty()) start_write(pClientContext); else start_read(pClientContext); } else if (pIoCtx->operation == OP_WRITE) { // Update the SendData buffer based on successful wire transfer if (dwBytesTransfered > 0 && dwBytesTransfered <= c.SendData.size()) { c.SendData.erase(c.SendData.begin(), c.SendData.begin() + dwBytesTransfered); // For SSL: dwBytesTransfered is the size of the ENCRYPTED record sent. // For TCP: It is the size of the plaintext sent. if (c.csock->_Type == sockettype::SSL) { ssl* sslSocket = static_cast<ssl*>(c.csock.get()); sslSocket->_send_off += dwBytesTransfered; // If the full TLS record is gone, we can send the next one if (sslSocket->_send_off >= sslSocket->_send_record.size()) { sslSocket->_send_record.clear(); sslSocket->_send_off = 0; // Note: We increment seq in sendDataWSA or here depending on logic } } if (c.SendData.empty()) { Loading @@ -200,7 +201,6 @@ namespace netplus { else start_read(pClientContext); } } catch (NetException& e) { // Ignore "Note" level exceptions, but disconnect on Errors if (e.getErrorType() != NetException::Note) { eargs->event->DisconnectEvent(c, tid, (ULONG_PTR)eargs->args); delete pClientContext; Loading src/ssl.cpp +197 −63 Original line number Diff line number Diff line Loading @@ -1777,89 +1777,223 @@ bool loadServerPrivateKeyDer(const std::string& keyDerPath); #ifdef Windows size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { // 1) During handshake, we send raw TCP (usually for ServerHello/Certificates) if (!_handshakeDone) { // Handshake logic usually handled via standard sendData // until the secure channel is established. return 0; return tcp::sendDataWSA(data, flags); } // 1. Encrypt the plaintext data into a TLS Record // This utilizes your existing internal encryption logic std::vector<uint8_t> plaintext( data.ptr ? data.data.ptr : data.data.buf, (data.ptr ? data.data.ptr : data.data.buf) + data.size auto throwSSL = [&](int etype, const std::string& msg) -> void { NetException e; e[etype] << "ssl::sendDataWSA: " << msg; throw e; }; // 2) IOCP Constraint: If a previous record is still being sent by the kernel, // we cannot start a new WSASend on this context. // In netplus IOCP, SendData usually manages the queue, but SSL needs to // ensure the record is fully formed before the raw send. if (!_send_record.empty() && _send_off < _send_record.size()) { return 0; // Kernel is still busy with the previous encrypted record } if (data.size == 0) return 0; // 3) Encapsulation: Build the TLS Record static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = (std::min)((size_t)data.size, TLS_MAX_PLAINTEXT); const uint8_t recordType = 0x17; // ApplicationData if (!_aes) throwSSL(NetException::Error, "_aes is null"); if (_mac_key.empty()) throwSSL(NetException::Error, "send MAC key missing"); // Copy plaintext for processing std::vector<uint8_t> content( (const uint8_t*)data.data.buf, (const uint8_t*)data.data.buf + take ); // This internal helper should append the encrypted record to _send_record _sendEncryptedRecord(this, 23, plaintext); // 23 = Application Data // Calculate HMAC (Sequence, Type, Version, Length, Content) std::vector<uint8_t> mac = _calculateHMAC(content, recordType, _send_seq, _mac_key); // TLS 1.2 Record Construction: [Content] + [MAC] + [Padding] std::vector<uint8_t> inner; inner.reserve(content.size() + mac.size() + 32); inner.insert(inner.end(), content.begin(), content.end()); inner.insert(inner.end(), mac.begin(), mac.end()); constexpr size_t block = 16; size_t rem = inner.size() % block; size_t padBytes = (rem == 0) ? block : (block - rem); uint8_t padVal = (uint8_t)(padBytes - 1); inner.insert(inner.end(), padBytes, padVal); // Generate Explicit IV (TLS 1.1+) std::vector<uint8_t> iv(block); { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } // Encrypt the inner block std::vector<uint8_t> ct = _aes->encryptCBC(inner, iv); const uint16_t fragLen = (uint16_t)(iv.size() + ct.size()); // 4) Prepare _send_record for the Overlapped I/O _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 (0x0303) _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); _send_record.insert(_send_record.end(), ct.begin(), ct.end()); _send_off = 0; // 5) Initiate Asynchronous Send // We pass the encrypted record to the parent TCP WSASend handler. // NOTE: In IOCP, _send_seq++ should happen in the EventWorker once // GetQueuedCompletionStatus confirms all bytes of the record were sent. buffer out((const char*)_send_record.data(), _send_record.size()); // 2. Prepare the encrypted buffer for the WSA call buffer wsa_out((char*)_send_record.data() + _send_off, _send_record.size() - _send_off); // This call triggers WSASend with the Overlapped structure. tcp::sendDataWSA(out, flags); // 3. Call the parent tcp implementation to initiate the IOCP write return tcp::sendDataWSA(wsa_out, flags); // Return 'take' to inform the application how many plaintext bytes were consumed return take; } size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { // 1. Move raw ciphertext from the network buffer (filled by IOCP) to the record processor if (!_rx_netbuf.empty()) { _recv_record.insert(_recv_record.end(), _rx_netbuf.begin(), _rx_netbuf.end()); _rx_netbuf.clear(); // Clear to allow the next WSARecv to fill it // If handshake is not done, we shouldn't be processing encrypted records yet if (!_handshakeDone) return 0; auto throwSSL = [&](int etype, const std::string& msg) -> void { NetException e; e[etype] << "ssl::recvDataWSA: " << msg; throw e; }; // 0) Serve already decrypted plaintext from the internal buffer first if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; // Reset buffer if fully consumed if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } // 2. We need at least 5 bytes to read the TLS Record Header if (_recv_record.size() < 5) { return 0; // 1) Loop to process the ciphertext currently in _rx_netbuf // Note: In IOCP, EventWorker has already appended raw bytes to _rx_netbuf for (;;) { // We need at least 5 bytes for a TLS Record Header if (_rx_netbuf.size() < 5) { return 0; // "Would block" - Wait for next IOCP completion } // 3. Parse TLS Record Header uint8_t type = _recv_record[0]; uint16_t version = (_recv_record[1] << 8) | _recv_record[2]; uint16_t length = (_recv_record[3] << 8) | _recv_record[4]; const uint8_t type = _rx_netbuf[0]; const uint16_t ver = (uint16_t(_rx_netbuf[1]) << 8) | uint16_t(_rx_netbuf[2]); const uint16_t recLen = (uint16_t(_rx_netbuf[3]) << 8) | uint16_t(_rx_netbuf[4]); // Validate TLS Major Version (Must be 3 for SSLv3/TLS 1.x) // This specifically prevents the "bad TLS major version" error if ((version >> 8) != 3) { NetException e; e[NetException::Error] << "ssl::recvDataWSA: bad TLS major version: " << (int)(version >> 8); throw e; static constexpr size_t TLS_MAX_RECORD = 16384 + 2048; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); } // 4. Check if the full encrypted record has arrived if (_recv_record.size() < (size_t)(5 + length)) { return 0; // Wait for more data from the next IOCP completion // Validate TLS Version (Standard check for TLS 1.2) if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } // 5. Extract the fragment (ciphertext + MAC + Padding) std::vector<uint8_t> fragment( _recv_record.begin() + 5, _recv_record.begin() + 5 + length ); const size_t total = 5 + (size_t)recLen; // 6. Decryption Logic std::vector<uint8_t> plaintext; if (!_handshakeDone) { // During handshake, data is often unencrypted (ClientHello/ServerHello) // or uses specific handshake encryption states. plaintext = fragment; } else { // Decrypt the application data using the established AES/CBC state plaintext = _decryptRecordCBC(type, version, fragment); // Ensure the full record is present in the buffer if (_rx_netbuf.size() < total) { return 0; // Record incomplete, wait for more data from IOCP } // 7. Copy results to the application buffer size_t toCopy = (std::min)(data.size, plaintext.size()); if (toCopy > 0) { memcpy(data.data.buf, plaintext.data(), toCopy); // Extract encrypted fragment and remove from raw network buffer std::vector<uint8_t> frag(_rx_netbuf.begin() + 5, _rx_netbuf.begin() + total); _rx_netbuf.erase(_rx_netbuf.begin(), _rx_netbuf.begin() + total); // Handle Handshake records mid-stream (re-keying or sequence updates) if (type == 0x16) { _recv_seq++; continue; } // 8. Maintenance: Remove the processed record from the internal stream _recv_record.erase(_recv_record.begin(), _recv_record.begin() + 5 + length); // Validate record type (Application Data = 0x17, Alert = 0x15) if (type != 0x17 && type != 0x15) { throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); } // 9. If there's a protocol-level requirement (like a Handshake Finished), // it should be processed here before returning to the user. // CBC explicit IV Decryption logic constexpr size_t block = 16; if (frag.size() < 2 * block || ((frag.size() - block) % block) != 0) { throwSSL(NetException::Error, "invalid CBC fragment size " + std::to_string(frag.size())); } if (!_aes_recv) throwSSL(NetException::Error, "_aes_recv is null"); std::vector<uint8_t> iv(frag.begin(), frag.begin() + block); std::vector<uint8_t> ciphertext(frag.begin() + block, frag.end()); // Decrypt using AES-CBC std::vector<uint8_t> plain = _aes_recv->decryptCBC(ciphertext, iv); // MAC and Padding Validation constexpr size_t macLen = 20; if (plain.size() < macLen + 1) throwSSL(NetException::Error, "bad padding/mac (too short)"); const uint8_t padLen = plain.back(); const size_t padBytes = size_t(padLen) + 1; if (padBytes > plain.size()) throwSSL(NetException::Error, "bad padding/mac (pad too large)"); return toCopy; const size_t noPadLen = plain.size() - padBytes; if (noPadLen < macLen) throwSSL(NetException::Error, "bad padding/mac (noPad < mac)"); // Verify Padding bytes for (size_t i = noPadLen; i < plain.size(); ++i) { if (plain[i] != padLen) throwSSL(NetException::Error, "bad padding bytes"); } const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); // Verify HMAC std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } _recv_seq++; // Buffer the plaintext _recv_record = std::move(content); _recv_off = 0; if (_recv_record.empty()) continue; // Copy decrypted data to the output buffer const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; // Cleanup internal buffer if finished if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } } #endif Loading
src/event/iocp.cpp +101 −101 Original line number Diff line number Diff line Loading @@ -93,63 +93,55 @@ namespace netplus { class EventWorker { public: /** * @brief Helper to initiate a raw overlapped read. * This fills the context buffer which recvDataWSA will later process. */ static void start_read(client* ctx) { con& c = *ctx->CurCon; // Point the WSA buffer to our persistent context buffer // Point the Overlapped buffer to our persistent context memory buffer buf(ctx->readCtx.buffer, BLOCKSIZE); if (c.csock->_Type == sockettype::TCP) { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); } else if (c.csock->_Type == sockettype::SSL) { // Note: For SSL, this initiates the raw TCP read into _rx_netbuf // This triggers the raw WSARecv. For SSL, it fills the internal _rx_netbuf. if (c.csock->_Type == sockettype::SSL) { static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, 0); } else { static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0); } } /** * @brief Helper to initiate a raw overlapped write. */ static void start_write(client* ctx) { con& c = *ctx->CurCon; if (c.SendData.empty()) return; // Take plaintext from SendData and pass it to the WSA method // For SSL, this will perform the encryption before calling WSASend size_t toSend = (std::min)((size_t)BLOCKSIZE, c.SendData.size()); buffer out(c.SendData.data(), toSend); if (c.csock->_Type == sockettype::TCP) { static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); } else if (c.csock->_Type == sockettype::SSL) { // This triggers SSL encryption then calls internal WSASend static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); size_t consumed = 0; if (c.csock->_Type == sockettype::SSL) { consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0); } else { consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0); } // Remove the plaintext from the queue that was actually "consumed" into a TLS record if (consumed > 0) c.SendData.erase(c.SendData.begin(), c.SendData.begin() + consumed); } /** * @brief The core IOCP Worker Loop */ EventWorker(int tid, ULONG_PTR args, EventWorkerArgs* eargs) { while (event::Running) { DWORD dwBytesTransfered = 0; ULONG_PTR lpContext = 0; OVERLAPPED* pOverlapped = nullptr; // 1. Wait for completion notification from Windows Kernel BOOL bReturn = GetQueuedCompletionStatus(eargs->eviocp, &dwBytesTransfered, &lpContext, &pOverlapped, eargs->timeout); // Handle timeout (eargs->timeout) if (lpContext == 0 && GetLastError() == WAIT_TIMEOUT) continue; if (lpContext == 0) continue; client* pClientContext = reinterpret_cast<client*>(lpContext); IO_CONTEXT* pIoCtx = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, overlapped); con& c = *pClientContext->CurCon; // 2. Handle Disconnection or Socket Errors if (!bReturn || (bReturn && dwBytesTransfered == 0)) { eargs->event->DisconnectEvent(c, tid, (ULONG_PTR)eargs->args); delete pClientContext; Loading @@ -163,33 +155,42 @@ namespace netplus { if (c.csock->_Type == sockettype::SSL) { ssl* sslSocket = static_cast<ssl*>(c.csock.get()); sslSocket->_rx_netbuf.insert( sslSocket->_rx_netbuf.end(), // 1. Move raw ciphertext from IOCP buffer to SSL's rx_netbuf sslSocket->_rx_netbuf.insert(sslSocket->_rx_netbuf.end(), pIoCtx->buffer, pIoCtx->buffer + dwBytesTransfered ); pIoCtx->buffer + dwBytesTransfered); // 2. Process all complete TLS records currently in the buffer buffer plain(BLOCKSIZE); size_t decrypted = sslSocket->recvDataWSA(plain, 0); if (decrypted > 0) { size_t decrypted = 0; while ((decrypted = sslSocket->recvDataWSA(plain, 0)) > 0) { c.RecvData.append(plain.data.buf, decrypted); eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); } } else { // Standard TCP: Raw data is the application data // Plain TCP c.RecvData.append(pIoCtx->buffer, dwBytesTransfered); eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args); } // Determine next step: Write if data pending, else keep reading // Continue the loop if (!c.SendData.empty()) start_write(pClientContext); else start_read(pClientContext); } else if (pIoCtx->operation == OP_WRITE) { // Update the SendData buffer based on successful wire transfer if (dwBytesTransfered > 0 && dwBytesTransfered <= c.SendData.size()) { c.SendData.erase(c.SendData.begin(), c.SendData.begin() + dwBytesTransfered); // For SSL: dwBytesTransfered is the size of the ENCRYPTED record sent. // For TCP: It is the size of the plaintext sent. if (c.csock->_Type == sockettype::SSL) { ssl* sslSocket = static_cast<ssl*>(c.csock.get()); sslSocket->_send_off += dwBytesTransfered; // If the full TLS record is gone, we can send the next one if (sslSocket->_send_off >= sslSocket->_send_record.size()) { sslSocket->_send_record.clear(); sslSocket->_send_off = 0; // Note: We increment seq in sendDataWSA or here depending on logic } } if (c.SendData.empty()) { Loading @@ -200,7 +201,6 @@ namespace netplus { else start_read(pClientContext); } } catch (NetException& e) { // Ignore "Note" level exceptions, but disconnect on Errors if (e.getErrorType() != NetException::Note) { eargs->event->DisconnectEvent(c, tid, (ULONG_PTR)eargs->args); delete pClientContext; Loading
src/ssl.cpp +197 −63 Original line number Diff line number Diff line Loading @@ -1777,89 +1777,223 @@ bool loadServerPrivateKeyDer(const std::string& keyDerPath); #ifdef Windows size_t netplus::ssl::sendDataWSA(buffer& data, int flags) { // 1) During handshake, we send raw TCP (usually for ServerHello/Certificates) if (!_handshakeDone) { // Handshake logic usually handled via standard sendData // until the secure channel is established. return 0; return tcp::sendDataWSA(data, flags); } // 1. Encrypt the plaintext data into a TLS Record // This utilizes your existing internal encryption logic std::vector<uint8_t> plaintext( data.ptr ? data.data.ptr : data.data.buf, (data.ptr ? data.data.ptr : data.data.buf) + data.size auto throwSSL = [&](int etype, const std::string& msg) -> void { NetException e; e[etype] << "ssl::sendDataWSA: " << msg; throw e; }; // 2) IOCP Constraint: If a previous record is still being sent by the kernel, // we cannot start a new WSASend on this context. // In netplus IOCP, SendData usually manages the queue, but SSL needs to // ensure the record is fully formed before the raw send. if (!_send_record.empty() && _send_off < _send_record.size()) { return 0; // Kernel is still busy with the previous encrypted record } if (data.size == 0) return 0; // 3) Encapsulation: Build the TLS Record static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = (std::min)((size_t)data.size, TLS_MAX_PLAINTEXT); const uint8_t recordType = 0x17; // ApplicationData if (!_aes) throwSSL(NetException::Error, "_aes is null"); if (_mac_key.empty()) throwSSL(NetException::Error, "send MAC key missing"); // Copy plaintext for processing std::vector<uint8_t> content( (const uint8_t*)data.data.buf, (const uint8_t*)data.data.buf + take ); // This internal helper should append the encrypted record to _send_record _sendEncryptedRecord(this, 23, plaintext); // 23 = Application Data // Calculate HMAC (Sequence, Type, Version, Length, Content) std::vector<uint8_t> mac = _calculateHMAC(content, recordType, _send_seq, _mac_key); // TLS 1.2 Record Construction: [Content] + [MAC] + [Padding] std::vector<uint8_t> inner; inner.reserve(content.size() + mac.size() + 32); inner.insert(inner.end(), content.begin(), content.end()); inner.insert(inner.end(), mac.begin(), mac.end()); constexpr size_t block = 16; size_t rem = inner.size() % block; size_t padBytes = (rem == 0) ? block : (block - rem); uint8_t padVal = (uint8_t)(padBytes - 1); inner.insert(inner.end(), padBytes, padVal); // Generate Explicit IV (TLS 1.1+) std::vector<uint8_t> iv(block); { std::random_device rd; for (auto& b : iv) b = (uint8_t)(rd() & 0xFF); } // Encrypt the inner block std::vector<uint8_t> ct = _aes->encryptCBC(inner, iv); const uint16_t fragLen = (uint16_t)(iv.size() + ct.size()); // 4) Prepare _send_record for the Overlapped I/O _send_record.clear(); _send_record.reserve(5 + fragLen); _send_record.push_back(recordType); _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2 (0x0303) _send_record.push_back((fragLen >> 8) & 0xFF); _send_record.push_back(fragLen & 0xFF); _send_record.insert(_send_record.end(), iv.begin(), iv.end()); _send_record.insert(_send_record.end(), ct.begin(), ct.end()); _send_off = 0; // 5) Initiate Asynchronous Send // We pass the encrypted record to the parent TCP WSASend handler. // NOTE: In IOCP, _send_seq++ should happen in the EventWorker once // GetQueuedCompletionStatus confirms all bytes of the record were sent. buffer out((const char*)_send_record.data(), _send_record.size()); // 2. Prepare the encrypted buffer for the WSA call buffer wsa_out((char*)_send_record.data() + _send_off, _send_record.size() - _send_off); // This call triggers WSASend with the Overlapped structure. tcp::sendDataWSA(out, flags); // 3. Call the parent tcp implementation to initiate the IOCP write return tcp::sendDataWSA(wsa_out, flags); // Return 'take' to inform the application how many plaintext bytes were consumed return take; } size_t netplus::ssl::recvDataWSA(buffer& data, int flags) { // 1. Move raw ciphertext from the network buffer (filled by IOCP) to the record processor if (!_rx_netbuf.empty()) { _recv_record.insert(_recv_record.end(), _rx_netbuf.begin(), _rx_netbuf.end()); _rx_netbuf.clear(); // Clear to allow the next WSARecv to fill it // If handshake is not done, we shouldn't be processing encrypted records yet if (!_handshakeDone) return 0; auto throwSSL = [&](int etype, const std::string& msg) -> void { NetException e; e[etype] << "ssl::recvDataWSA: " << msg; throw e; }; // 0) Serve already decrypted plaintext from the internal buffer first if (_recv_off < _recv_record.size()) { const size_t avail = _recv_record.size() - _recv_off; const size_t outLen = (std::min)((size_t)data.size, avail); std::memcpy(data.data.buf, _recv_record.data() + _recv_off, outLen); _recv_off += outLen; // Reset buffer if fully consumed if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } // 2. We need at least 5 bytes to read the TLS Record Header if (_recv_record.size() < 5) { return 0; // 1) Loop to process the ciphertext currently in _rx_netbuf // Note: In IOCP, EventWorker has already appended raw bytes to _rx_netbuf for (;;) { // We need at least 5 bytes for a TLS Record Header if (_rx_netbuf.size() < 5) { return 0; // "Would block" - Wait for next IOCP completion } // 3. Parse TLS Record Header uint8_t type = _recv_record[0]; uint16_t version = (_recv_record[1] << 8) | _recv_record[2]; uint16_t length = (_recv_record[3] << 8) | _recv_record[4]; const uint8_t type = _rx_netbuf[0]; const uint16_t ver = (uint16_t(_rx_netbuf[1]) << 8) | uint16_t(_rx_netbuf[2]); const uint16_t recLen = (uint16_t(_rx_netbuf[3]) << 8) | uint16_t(_rx_netbuf[4]); // Validate TLS Major Version (Must be 3 for SSLv3/TLS 1.x) // This specifically prevents the "bad TLS major version" error if ((version >> 8) != 3) { NetException e; e[NetException::Error] << "ssl::recvDataWSA: bad TLS major version: " << (int)(version >> 8); throw e; static constexpr size_t TLS_MAX_RECORD = 16384 + 2048; if (recLen == 0 || recLen > TLS_MAX_RECORD) { throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen)); } // 4. Check if the full encrypted record has arrived if (_recv_record.size() < (size_t)(5 + length)) { return 0; // Wait for more data from the next IOCP completion // Validate TLS Version (Standard check for TLS 1.2) if (ver != 0x0303) { throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver)); } // 5. Extract the fragment (ciphertext + MAC + Padding) std::vector<uint8_t> fragment( _recv_record.begin() + 5, _recv_record.begin() + 5 + length ); const size_t total = 5 + (size_t)recLen; // 6. Decryption Logic std::vector<uint8_t> plaintext; if (!_handshakeDone) { // During handshake, data is often unencrypted (ClientHello/ServerHello) // or uses specific handshake encryption states. plaintext = fragment; } else { // Decrypt the application data using the established AES/CBC state plaintext = _decryptRecordCBC(type, version, fragment); // Ensure the full record is present in the buffer if (_rx_netbuf.size() < total) { return 0; // Record incomplete, wait for more data from IOCP } // 7. Copy results to the application buffer size_t toCopy = (std::min)(data.size, plaintext.size()); if (toCopy > 0) { memcpy(data.data.buf, plaintext.data(), toCopy); // Extract encrypted fragment and remove from raw network buffer std::vector<uint8_t> frag(_rx_netbuf.begin() + 5, _rx_netbuf.begin() + total); _rx_netbuf.erase(_rx_netbuf.begin(), _rx_netbuf.begin() + total); // Handle Handshake records mid-stream (re-keying or sequence updates) if (type == 0x16) { _recv_seq++; continue; } // 8. Maintenance: Remove the processed record from the internal stream _recv_record.erase(_recv_record.begin(), _recv_record.begin() + 5 + length); // Validate record type (Application Data = 0x17, Alert = 0x15) if (type != 0x17 && type != 0x15) { throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type)); } // 9. If there's a protocol-level requirement (like a Handshake Finished), // it should be processed here before returning to the user. // CBC explicit IV Decryption logic constexpr size_t block = 16; if (frag.size() < 2 * block || ((frag.size() - block) % block) != 0) { throwSSL(NetException::Error, "invalid CBC fragment size " + std::to_string(frag.size())); } if (!_aes_recv) throwSSL(NetException::Error, "_aes_recv is null"); std::vector<uint8_t> iv(frag.begin(), frag.begin() + block); std::vector<uint8_t> ciphertext(frag.begin() + block, frag.end()); // Decrypt using AES-CBC std::vector<uint8_t> plain = _aes_recv->decryptCBC(ciphertext, iv); // MAC and Padding Validation constexpr size_t macLen = 20; if (plain.size() < macLen + 1) throwSSL(NetException::Error, "bad padding/mac (too short)"); const uint8_t padLen = plain.back(); const size_t padBytes = size_t(padLen) + 1; if (padBytes > plain.size()) throwSSL(NetException::Error, "bad padding/mac (pad too large)"); return toCopy; const size_t noPadLen = plain.size() - padBytes; if (noPadLen < macLen) throwSSL(NetException::Error, "bad padding/mac (noPad < mac)"); // Verify Padding bytes for (size_t i = noPadLen; i < plain.size(); ++i) { if (plain[i] != padLen) throwSSL(NetException::Error, "bad padding bytes"); } const size_t contentLen = noPadLen - macLen; std::vector<uint8_t> content(plain.begin(), plain.begin() + contentLen); std::vector<uint8_t> recvMac(plain.begin() + contentLen, plain.begin() + noPadLen); // Verify HMAC std::vector<uint8_t> calcMac = _calculateHMAC(content, type, _recv_seq, _client_mac_key); if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) { throwSSL(NetException::Error, "bad mac"); } _recv_seq++; // Buffer the plaintext _recv_record = std::move(content); _recv_off = 0; if (_recv_record.empty()) continue; // Copy decrypted data to the output buffer const size_t outLen = (std::min)((size_t)data.size, _recv_record.size()); std::memcpy(data.data.buf, _recv_record.data(), outLen); _recv_off = outLen; // Cleanup internal buffer if finished if (_recv_off == _recv_record.size()) { _recv_record.clear(); _recv_off = 0; } return outLen; } } #endif