Commit 2b1defdc authored by jan.koester's avatar jan.koester
Browse files

test

parent 0ffa777c
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -180,10 +180,10 @@ namespace netplus {

            // 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);
                static_cast<ssl*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0);
            }
            else {
                static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, 0);
                static_cast<tcp*>(c.csock.get())->recvDataWSA(buf, &ctx->readCtx.overlapped, 0);
            }
        }

@@ -201,12 +201,14 @@ namespace netplus {
            size_t toSend = (std::min)((size_t)BLOCKSIZE, c.SendData.size());
            buffer out(c.SendData.data(), toSend);

            // Ensure the socket's internal overlapped is NOT used implicitly;
            // EventWorker will pass the client's persistent write overlapped to the socket.
            size_t consumed = 0;
            if (c.csock->_Type == sockettype::SSL) {
                consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, 0);
                consumed = static_cast<ssl*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0);
            }
            else {
                consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, 0);
                consumed = static_cast<tcp*>(c.csock.get())->sendDataWSA(out, &ctx->writeCtx.overlapped, 0);
            }

            // Remove the plaintext from the queue that was actually "consumed" into a TLS record
@@ -320,7 +322,7 @@ namespace netplus {
                            // 2. Process all complete TLS records currently in the buffer
                            buffer plain(BLOCKSIZE);
                            size_t decrypted = 0;
                            while ((decrypted = sslSocket->recvDataWSA(plain, 0)) > 0) {
                            while ((decrypted = sslSocket->recvDataWSA(plain, nullptr, 0)) > 0) {
                                c.RecvData.append(plain.data.buf, decrypted);
                                std::cerr << "[IOCP] RequestEvent (SSL) fd=" << c.csock->fd() << " appending " << decrypted << " bytes total_recv=" << c.RecvData.size() << std::endl;
                                eargs->event->RequestEvent(c, tid, (ULONG_PTR)eargs->args);
+8 −8
Original line number Diff line number Diff line
@@ -107,8 +107,8 @@ namespace netplus {
            virtual void             accept(std::unique_ptr<socket> &csock)=0;
#ifdef Windows 
            virtual void             accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock)=0;
            virtual size_t           sendDataWSA(buffer &data, int flags = 0)=0;
            virtual size_t           recvDataWSA(buffer &data, int flags = 0)=0;
            virtual size_t           sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0;
            virtual size_t           recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags)=0;
#endif
            virtual void             bind()=0;
            virtual void             listen()=0;
@@ -160,8 +160,8 @@ namespace netplus {
            ~tcp();
#ifdef Windows
            void          accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock);
            size_t        sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData
            size_t        recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData
            size_t        sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
            size_t        recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
#endif
            void          accept(std::unique_ptr <socket> &csock);
            void          bind();
@@ -196,8 +196,8 @@ namespace netplus {
            void          accept(std::unique_ptr<socket> &csock);
#ifdef Windows
            void          accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket> &csock);
            size_t        sendDataWSA(buffer &data, int flags = 0); // Renamed from sendData
            size_t        recvDataWSA(buffer &data, int flags = 0); // Renamed from recvData
            size_t        sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
            size_t        recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
#endif
            void          bind();
            void          listen();
@@ -237,8 +237,8 @@ namespace netplus {
        size_t recvData(buffer &data, int flags = 0) override;
#ifdef Windows
        void accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) override;
        size_t sendDataWSA(buffer &data, int flags = 0);
        size_t recvDataWSA(buffer &data, int flags = 0);
        size_t sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
        size_t recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags);
#endif
        bool loadServerPrivateKeyDer(const std::string& keyDerPath);

+137 −181
Original line number Diff line number Diff line
@@ -296,7 +296,7 @@ namespace netplus {

        if (len == 0 || len > TLS_MAX_RECORD) {
            netplus::NetException e;
            e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << len;
            e[netplus::NetException::Error] << "ssl::accept: invalid TLS record length " << static_cast<unsigned long long>(len);
            throw e;
        }

@@ -308,11 +308,20 @@ namespace netplus {
    }
};

netplus::ssl::ssl(const netplus::x509cert &cert) : _cert(cert) , _aes(nullptr) {
netplus::ssl::ssl(const netplus::x509cert &cert) :
    _cert(cert),
    _aes(nullptr),
    _handshakeDone(false)
{
    _Type=sockettype::SSL;
}

netplus::ssl::ssl(const netplus::x509cert &cert,int sock) :  tcp (sock), _cert(cert) ,_aes(nullptr)  {
netplus::ssl::ssl(const netplus::x509cert &cert,int sock) :
    tcp(sock),
    _cert(cert),
    _aes(nullptr),
    _handshakeDone(false)
{
    _Type=sockettype::SSL;
};

@@ -1004,7 +1013,8 @@ void netplus::ssl::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& c

    // Create accept socket (overlapped)
    const addrinfo* ai = reinterpret_cast<const addrinfo*>(this->_SocketInfo);
    cssock->_Socket = WSASocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED);
    // Use the wide-char variant to avoid deprecated-API warning on MSVC.
    cssock->_Socket = WSASocketW(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED);
    if (cssock->_Socket == INVALID_SOCKET) {
        int err = WSAGetLastError();
        exception[NetException::Error] << "AcceptEx: WSASocket failed: " << err;
@@ -1485,7 +1495,7 @@ size_t netplus::ssl::sendData(buffer& data, int flags) {
    _send_record.clear();
    _send_record.reserve(5 + fragLen);
    _send_record.push_back(recordType);
    _send_record.push_back(0x03); _send_record.push_back(0x03);
    _send_record.push_back(0x03); _send_record.push_back(0x03); // TLS 1.2
    _send_record.push_back((fragLen >> 8) & 0xFF);
    _send_record.push_back(fragLen & 0xFF);
    _send_record.insert(_send_record.end(), iv.begin(), iv.end());
@@ -1511,7 +1521,7 @@ size_t netplus::ssl::recvData(buffer& data, int flags) {
    // 0) Serve buffered plaintext 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);
        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;
        data.size = outLen;
@@ -1568,10 +1578,10 @@ size_t netplus::ssl::recvData(buffer& data, int flags) {
        static constexpr size_t TLS_MAX_RECORD       = TLS_MAX_PLAINTEXT + TLS_MAX_CBC_OVERHEAD;

        if (recLen == 0 || recLen > TLS_MAX_RECORD) {
            throwSSL(NetException::Error, "invalid TLS record length " + std::to_string(recLen));
            throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen));
        }
        if (ver != 0x0303) {
            throwSSL(NetException::Error, "unexpected TLS version " + std::to_string(ver));
            throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver));
        }

        const size_t total = 5 + (size_t)recLen;
@@ -1629,14 +1639,11 @@ size_t netplus::ssl::recvData(buffer& data, int flags) {
        }

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

        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()))
        {

        if (calcMac.size() != recvMac.size() || !std::equal(calcMac.begin(), calcMac.end(), recvMac.begin())) {
            throwSSL(NetException::Error, "bad mac");
        }

@@ -1651,16 +1658,13 @@ size_t netplus::ssl::recvData(buffer& data, int flags) {
            continue;
        }

        const size_t outLen = std::min<size_t>(data.size, _recv_record.size());
        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;
        data.size = outLen;

        if (_recv_off == _recv_record.size()) {
            _recv_record.clear();
            _recv_off = 0;
        }

        return outLen;
    }
}
@@ -1834,13 +1838,15 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC(uint8_t recType,
    return content;              // content = handshake/appdata plaintext (no mac/pad)
}

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)
// Ensure WSA-overlapped variants required by the Windows IOCP path are defined.
// These forward to the non-overlapped logic where appropriate and perform
// TLS record framing for IOCP usage.

size_t netplus::ssl::sendDataWSA(buffer & data, WSAOVERLAPPED * pOv, int flags) {
        // During handshake, forward to raw TCP overlapped send
        if (!_handshakeDone) {
        return tcp::sendDataWSA(data, flags);
            return tcp::sendDataWSA(data, pOv, flags);
        }
    
        auto throwSSL = [&](int etype, const std::string& msg) -> void {
@@ -1849,35 +1855,28 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) {
            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 a previous record is still being sent, don't start another
        if (!_send_record.empty() && _send_off < _send_record.size()) {
        return 0; // Kernel is still busy with the previous encrypted record
            return 0;
        }
 
        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
        );
    
    // 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());
@@ -1889,41 +1888,36 @@ size_t netplus::ssl::sendDataWSA(buffer& data, int flags) {
        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); }
        { 
            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(0x03); _send_record.push_back(0x03); // TLS 1.2
        _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());
    
    // This call triggers WSASend with the Overlapped structure.
    tcp::sendDataWSA(out, flags);
        // Start overlapped send on underlying TCP
        tcp::sendDataWSA(out, pOv, flags);
    
    // Return 'take' to inform the application how many plaintext bytes were consumed
        // Return number of plaintext bytes consumed
        return take;    
}

size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {
    // If handshake is not done, we shouldn't be processing encrypted records yet
size_t netplus::ssl::recvDataWSA(buffer & data, WSAOVERLAPPED* /*pOv*/, int flags) {
        // For IOCP the raw bytes are appended to _rx_netbuf by the EventWorker;
        // this method decodes any complete TLS records present in _rx_netbuf.
        if (!_handshakeDone) return 0;
    
        auto throwSSL = [&](int etype, const std::string& msg) -> void {
@@ -1932,14 +1926,12 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {
            throw e;
        };
    
    // 0) Serve already decrypted plaintext from the internal buffer first
        // 0) Serve buffered plaintext
        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;
@@ -1947,52 +1939,31 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {
            return outLen;
        }
   
    // 1) Loop to process the ciphertext currently in _rx_netbuf
    // Note: In IOCP, EventWorker has already appended raw bytes to _rx_netbuf
        // 1) Process ciphertext already present in _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
        }
        if (_rx_netbuf.size() < 5) return 0; // need header
        
        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]);
        
        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));
            throwSSL(NetException::Error, "invalid TLS record length " + std::to_string((unsigned long long)recLen));
        }
        
        // Validate TLS Version (Standard check for TLS 1.2)
        if (ver != 0x0303) {
            throwSSL(NetException::Error, "unexpected TLS version 0x" + std::to_string(ver));
        }
        
        const size_t total = 5 + (size_t)recLen;
        if (_rx_netbuf.size() < total) return 0; // incomplete

        // Ensure the full record is present in the buffer
        if (_rx_netbuf.size() < total) {
            return 0; // Record incomplete, wait for more data from IOCP
        }

        // 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; 
        }

        // 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));
        }
        if (type == 0x16) { _recv_seq++; continue; }
        if (type != 0x17 && type != 0x15) throwSSL(NetException::Error, "unsupported TLS record type " + std::to_string((int)type));

        // 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()));
@@ -2002,11 +1973,8 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {

        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)");
        
@@ -2016,8 +1984,6 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {
        
        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");
        }
@@ -2025,36 +1991,26 @@ size_t netplus::ssl::recvDataWSA(buffer& data, int flags) {
        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()))
        {
        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

src/windows/ssl.cpp

0 → 100644
+1200 −0

File added.

Preview size limit exceeded, changes collapsed.

+6 −7
Original line number Diff line number Diff line
@@ -365,15 +365,14 @@ void netplus::tcp::getAddress(std::string& addr) {
    }
}

// tcp.cpp
size_t netplus::tcp::sendDataWSA(buffer& data, int flags) {
// New overloads that accept explicit OVERLAPPED pointer
size_t netplus::tcp::sendDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) {
    NetException exception;
    _wsaBuf.buf = data.ptr ? (char*)data.data.ptr : data.data.buf;
    _wsaBuf.len = static_cast<ULONG>(data.size);

    DWORD dwBytes = 0;
    // Note: We do not reset _Overlapped here because it is managed by the IOCP context
    int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, &_Overlapped, nullptr);
    int rval = ::WSASend(_Socket, &_wsaBuf, 1, &dwBytes, flags, pOv, nullptr);

    if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
        exception[NetException::Error] << "WSASend failed: " << GetLastError();
@@ -382,14 +381,14 @@ size_t netplus::tcp::sendDataWSA(buffer& data, int flags) {
    return (size_t)dwBytes;
}

size_t netplus::tcp::recvDataWSA(buffer& data, int flags) {
size_t netplus::tcp::recvDataWSA(buffer& data, WSAOVERLAPPED* pOv, int flags) {
    NetException exception;
    _wsaBuf.buf = data.data.buf;
    _wsaBuf.len = static_cast<ULONG>(data.size);

    DWORD dwBytes = 0;
    DWORD dwFlags = (DWORD)flags;
    int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, &_Overlapped, nullptr);
    int rval = ::WSARecv(_Socket, &_wsaBuf, 1, &dwBytes, &dwFlags, pOv, nullptr);

    if (rval == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
        exception[NetException::Error] << "WSARecv failed: " << GetLastError();
Loading