Commit 27e13fba authored by jan.koester's avatar jan.koester
Browse files

test

parent fbf748c7
Loading
Loading
Loading
Loading
+101 −101
Original line number Diff line number Diff line
@@ -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;
@@ -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()) {
@@ -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;
+197 −63
Original line number Diff line number Diff line
@@ -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