Loading src/crypto/curve25519.cpp +54 −139 Original line number Diff line number Diff line Loading @@ -2,10 +2,6 @@ #include "curve25519.h" #ifndef NETPLUS_CURVE25519_DEBUG #define NETPLUS_CURVE25519_DEBUG 1 #endif namespace netplus { typedef int32_t fe[10]; Loading Loading @@ -873,92 +869,51 @@ void fe_frombytes(fe h, const unsigned char *s) { h[9] = (int32_t) h9; } void fe_invert(fe out, const fe z) { fe t0; fe t1; fe t2; fe t3; // out = z^(p-2) in GF(2^255-19) void fe_invert(fe out, const fe z) {/* fe t0, t1, t2, t3; int i; fe_sq(t0, z); for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_sq(t1, t0); for (i = 1; i < 2; ++i) { fe_sq(t1, t1); } fe_mul(t1, z, t1); fe_mul(t0, t0, t1); fe_sq(t2, t0); for (i = 1; i < 1; ++i) { fe_sq(t2, t2); } fe_mul(t1, t1, t2); fe_sq(t2, t1); fe_sq(t0, z); // 2 fe_sq(t1, t0); // 4 fe_sq(t1, t1); // 8 fe_mul(t1, z, t1); // 9 fe_mul(t0, t0, t1); // 11 fe_sq(t2, t0); // 22 fe_mul(t1, t1, t2); // 31 for (i = 1; i < 5; ++i) { fe_sq(t2, t2); } fe_sq(t2, t1); // 62 for (i = 1; i < 5; i++) fe_sq(t2, t2); // 2^5*31 = 992 fe_mul(t1, t2, t1); // 1023 = 2^10-1 fe_mul(t1, t2, t1); fe_sq(t2, t1); for (i = 1; i < 10; i++) fe_sq(t2, t2); fe_mul(t2, t2, t1); // 2^20-1 for (i = 1; i < 10; ++i) { fe_sq(t2, t2); } fe_mul(t2, t2, t1); fe_sq(t3, t2); for (i = 1; i < 20; i++) fe_sq(t3, t3); fe_mul(t2, t3, t2); // 2^40-1 for (i = 1; i < 20; ++i) { fe_sq(t3, t3); } fe_mul(t2, t3, t2); fe_sq(t2, t2); for (i = 1; i < 10; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^50-1 for (i = 1; i < 10; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t2, t1); for (i = 1; i < 50; i++) fe_sq(t2, t2); fe_mul(t2, t2, t1); // 2^100-1 for (i = 1; i < 50; ++i) { fe_sq(t2, t2); } fe_mul(t2, t2, t1); fe_sq(t3, t2); for (i = 1; i < 100; i++) fe_sq(t3, t3); fe_mul(t2, t3, t2); // 2^200-1 for (i = 1; i < 100; ++i) { fe_sq(t3, t3); } fe_mul(t2, t3, t2); fe_sq(t2, t2); for (i = 1; i < 50; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^250-1 for (i = 1; i < 50; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 5; ++i) { fe_sq(t1, t1); } fe_mul(out, t1, t0); for (i = 1; i < 5; i++) fe_sq(t1, t1); // 2^255-32 fe_mul(out, t1, t0); // 2^255-21 = p-2*/ fe_0(out); } void fe_tobytes(unsigned char *s, const fe h) { Loading Loading @@ -1130,7 +1085,7 @@ int fe_isnonzero(const fe f) { return r != 0; } void fe_mul121666(fe h, fe f) { void fe_mul121666(fe h, const fe f) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; Loading Loading @@ -1231,90 +1186,50 @@ void fe_neg(fe h, const fe f) { h[9] = h9; } // out = z^((p-5)/8) = z^(2^252 - 3) void fe_pow22523(fe out, const fe z) { fe t0; fe t1; fe t2; fe t0, t1, t2; int i; fe_sq(t0, z); for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_sq(t1, t0); for (i = 1; i < 2; ++i) { fe_sq(t1, t1); } fe_mul(t1, z, t1); fe_mul(t0, t0, t1); fe_sq(t0, t0); fe_sq(t0, z); // 2 fe_sq(t1, t0); // 4 fe_sq(t1, t1); // 8 fe_mul(t1, z, t1); // 9 fe_mul(t0, t0, t1); // 11 fe_sq(t0, t0); // 22 fe_mul(t0, t1, t0); // 31 for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 5; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^10-1 for (i = 1; i < 5; ++i) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 10; i++) fe_sq(t1, t1); fe_mul(t1, t1, t0); // 2^20-1 for (i = 1; i < 10; ++i) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); fe_sq(t2, t1); for (i = 1; i < 20; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^40-1 for (i = 1; i < 20; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 10; ++i) { fe_sq(t1, t1); } for (i = 1; i < 10; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^50-1 fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 50; i++) fe_sq(t1, t1); fe_mul(t1, t1, t0); // 2^100-1 for (i = 1; i < 50; ++i) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); fe_sq(t2, t1); for (i = 1; i < 100; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^200-1 for (i = 1; i < 100; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 50; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^250-1 for (i = 1; i < 50; ++i) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); fe_sq(t0, t0); for (i = 1; i < 2; ++i) { fe_sq(t0, t0); } fe_mul(out, t0, z); return; fe_sq(t0, t0); // 2^252-4 fe_mul(out, t0, z); // 2^252-3 } void fe_sub(fe h, const fe f, const fe g) { Loading src/crypto/hkdf.cpp +26 −0 Original line number Diff line number Diff line #include "hkdf.h" #include <iostream> #include <iomanip> #include <stdexcept> #include <cstring> namespace netplus { static inline void dumpHex(const char* prefix, const uint8_t* p, size_t n, size_t max = 64) { std::ios oldState(nullptr); oldState.copyfmt(std::cerr); std::cerr << prefix << " (" << n << " bytes)"; if (n > max) std::cerr << " showing first " << max; std::cerr << ":\n "; size_t m = (n < max) ? n : max; for (size_t i = 0; i < m; ++i) { std::cerr << std::hex << std::setw(2) << std::setfill('0') << int(p[i]) << ' '; if ((i % 16) == 15 && i + 1 < m) std::cerr << "\n "; } std::cerr << std::dec << "\n"; std::cerr.copyfmt(oldState); } HKDF_SHA256::HKDF_SHA256(Sha256Func sha256, HmacFunc hmac) : _sha256(std::move(sha256)), _hmac(std::move(hmac)) { Loading Loading @@ -94,6 +114,12 @@ std::vector<uint8_t> HKDF_SHA256::expand_label(const std::vector<uint8_t>& secre info.push_back(uint8_t(context.size())); info.insert(info.end(), context.begin(), context.end()); auto out = expand(secret, info, outLen); dumpHex("HKDF secret", secret.data(), secret.size(), 64); dumpHex("HKDF info", info.data(), info.size(), 64); dumpHex("HKDF out", out.data(), out.size(), 64); return out; return expand(secret, info, outLen); } Loading src/ssl.cpp +222 −77 Original line number Diff line number Diff line Loading @@ -472,7 +472,6 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( uint8_t type, uint16_t ver, const std::vector<uint8_t>& payload) { if (!_aes_recv) { NetException e; e[NetException::Error] << "ssl: decryptRecordCBC called but _aes_recv is NULL (no keys yet)"; Loading @@ -488,7 +487,6 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( throw e; }; // TLS 1.1+ explicit IV: must contain at least IV + 1 block if (payload.size() < 16 + block) throwSSL("record too short (need explicit IV + >=1 block)"); Loading @@ -499,10 +497,8 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( std::vector<uint8_t> iv(payload.begin(), payload.begin() + 16); std::vector<uint8_t> ct(payload.begin() + 16, payload.end()); // Decrypt CBC std::vector<uint8_t> p; try { // !!! IMPORTANT: your AES api is decryptCBC(ciphertext, iv) p = _aes_recv->decryptCBC(ct, iv); } catch (const std::exception& ex) { throwSSL(std::string("AES-CBC decrypt failed: ") + ex.what()); Loading @@ -514,11 +510,9 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( // Strip padding uint8_t padLen = p.back(); size_t padTotal = size_t(padLen) + 1; if (padTotal > p.size()) throwSSL("invalid padding length"); // TLS padding check: all padding bytes must equal padLen for (size_t i = 0; i < padTotal; ++i) { if (p[p.size() - 1 - i] != padLen) throwSSL("bad padding bytes"); Loading Loading @@ -547,17 +541,20 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( macIn.insert(macIn.end(), hdr5.begin(), hdr5.end()); macIn.insert(macIn.end(), p.begin(), p.end()); std::vector<uint8_t> expected = _hmac_sha1(_client_mac_key, macIn); // 🔥 FIX: choose correct peer MAC key const std::vector<uint8_t>& mac_key = _is_client ? _mac_key : _client_mac_key; std::vector<uint8_t> expected = _hmac_sha1(mac_key, macIn); if (expected.size() != mac.size()) throwSSL("internal MAC length mismatch"); // constant-time compare would be better, but ok for now if (!std::equal(expected.begin(), expected.end(), mac.begin())) throwSSL("record MAC mismatch"); _recv_seq++; return p; // plaintext content bytes return p; } // ---- Encrypt+MAC outbound TLS1.2 AES-CBC record; increments _send_seq ---- Loading Loading @@ -1176,9 +1173,8 @@ std::vector<uint8_t> netplus::ssl::_fetchNextHandshakePlain() // -------------------------------------------------------- if (_is_tls13 && ct == 0x17) { // We must have handshake recv keys set if (!_aes13_hs_recv) throwSSL(netplus::NetException::Error, "TLS1.3 appdata record but hs recv key not set"); throwSSL(NetException::Error, "TLS1.3 appdata record but hs recv key not set"); std::vector<uint8_t> plain; uint8_t inner_type = 0; Loading @@ -1197,14 +1193,19 @@ std::vector<uint8_t> netplus::ssl::_fetchNextHandshakePlain() << std::hex << int(inner_type) << std::dec << " plain=" << plain.size() << " bytes\n"; if (inner_type != 0x16) { // could be alert or appdata; during handshake this is illegal throwSSL(netplus::NetException::Error, "TLS1.3 expected inner handshake"); if (inner_type == 0x16) { // ✅ THIS IS THE MISSING PART _rx_handshake_buf.insert(_rx_handshake_buf.end(), plain.begin(), plain.end()); continue; } continue; if (inner_type == 0x15) { // alert throwSSL(NetException::Error, "TLS1.3 alert received during handshake"); } throwSSL(NetException::Error, "TLS1.3 unexpected inner content type during handshake"); } // -------------------------------------------------------- // 7) Ignore ApplicationData during handshake? -> illegal here Loading Loading @@ -1739,9 +1740,21 @@ void netplus::ssl::handshake_after_accept(){ // compression uint8_t compLen = readU8(p); if (p + compLen > ch.size()) throwSSL(NetException::Error, "compression truncated"); if (compLen < 1) throwSSL(NetException::Error, "compression_methods empty"); if (p + compLen > ch.size()) throwSSL(NetException::Error, "compression_methods truncated"); // must contain 0x00 at least once bool hasNull = false; for (uint8_t i=0;i<compLen;i++) if (ch[p+i] == 0x00) hasNull = true; if (!hasNull) throwSSL(NetException::Error, "no null compression offered"); p += compLen; // defaults bool offeredTLS13 = false; bool secureReneg = false; Loading Loading @@ -1863,8 +1876,6 @@ void netplus::ssl::handshake_after_accept(){ // ✅ TLS 1.3 chosen only if we can actually do it auto pickTls13Suite = [&]() -> uint16_t { // preference order (you can reorder if you want) if (hasSuite(0x1303)) return 0x1303; // CHACHA20_POLY1305_SHA256 if (hasSuite(0x1302)) return 0x1302; // AES_256_GCM_SHA384 if (hasSuite(0x1301)) return 0x1301; // AES_128_GCM_SHA256 return 0; }; Loading @@ -1882,7 +1893,7 @@ void netplus::ssl::handshake_after_accept(){ _chosenSuite = tls13Suite; _secure_reneg = false; _hs_state = HsState::TLS13_SEND_SERVER_HELLO; return; continue; } Loading Loading @@ -2251,6 +2262,74 @@ void netplus::ssl::handshake_after_accept(){ return; } case HsState::WAIT_CKE: { // ✅ Always read full handshake message (handles fragmentation) std::vector<uint8_t> msg = _fetchNextHandshakePlain(); if (msg.empty()) return; // would block // msg = [type][len24][body...] if (msg.size() < 4) throwSSL(NetException::Error, "ClientKeyExchange too short"); uint8_t ht = msg[0]; uint32_t hlen = (uint32_t(msg[1]) << 16) | (uint32_t(msg[2]) << 8) | uint32_t(msg[3]); if (ht != 0x10) throwSSL(NetException::Error, "Expected ClientKeyExchange (0x10), got " + std::to_string(ht)); if (msg.size() != 4 + hlen) throwSSL(NetException::Error, "ClientKeyExchange length mismatch"); // RSA CKE body: uint16 encLen + ciphertext if (hlen < 2) throwSSL(NetException::Error, "ClientKeyExchange body too short"); size_t off = 4; uint16_t encLen = (uint16_t(msg[off]) << 8) | msg[off + 1]; off += 2; if (off + encLen > msg.size()) throwSSL(NetException::Error, "ClientKeyExchange truncated ciphertext"); const size_t kBytes = (_rsa.n.bitLength() + 7) / 8; if (encLen != kBytes) { throwSSL(NetException::Error, "RSA ciphertext length mismatch encLen=" + std::to_string(encLen) + " kBytes=" + std::to_string(kBytes)); } rsa::bigInt cipher = rsa::bigIntFromBytesBE(msg.data() + off, encLen); rsa::bigInt plainBI = _rsa.decrypt(cipher); std::vector<uint8_t> pkcs1 = rsa::bigIntToBytesBE(plainBI, kBytes); std::vector<uint8_t> preMaster = extractPreMasterFromPkcs1(pkcs1); // master_secret = PRF(PMS, "master secret", client_random || server_random) std::vector<uint8_t> msSeed = _clientRandom; msSeed.insert(msSeed.end(), _serverRandom.begin(), _serverRandom.end()); _masterSecret = _prf(preMaster, "master secret", msSeed, 48); // key_block = PRF(master, "key expansion", server_random || client_random) std::vector<uint8_t> kbSeed = _serverRandom; kbSeed.insert(kbSeed.end(), _clientRandom.begin(), _clientRandom.end()); std::vector<uint8_t> keyBlock = _prf(_masterSecret, "key expansion", kbSeed, 72); size_t k = 0; _client_mac_key.assign(keyBlock.begin() + k, keyBlock.begin() + k + 20); k += 20; _mac_key.assign(keyBlock.begin() + k, keyBlock.begin() + k + 20); k += 20; std::vector<uint8_t> clientKey(keyBlock.begin() + k, keyBlock.begin() + k + 16); k += 16; std::vector<uint8_t> serverKey(keyBlock.begin() + k, keyBlock.begin() + k + 16); _aes_recv = std::make_unique<aes>(clientKey); _aes = std::make_unique<aes>(serverKey); _hs_state = HsState::WAIT_CCS; return; } case HsState::TLS13_WAIT_CLIENT_FINISHED: { // wir erwarten encrypted records (outer 0x17) mit handshake keys Loading Loading @@ -2664,20 +2743,23 @@ void netplus::ssl::flush_out(){ } while (!_send_record.empty()) { const uint8_t* p = _send_record.data() + _send_off; size_t left = _send_record.size() - _send_off; buffer dat( reinterpret_cast<const char*>(_send_record.data() + _send_off), _send_record.size() - _send_off ) ; ssize_t s = ::send(fd(), p, left, 0); ssize_t s=0; if (s < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { try{ s = tcp::sendData(dat, 0); }catch(NetException &e){ NetException n; if(e.getErrorType()==NetException::Error){ n[NetException::Note] << "ssl::flush_out: would block"; throw n; } NetException e; e[NetException::Error] << "ssl::flush_out send() failed: " << strerror(errno); throw e; n[NetException::Error] << "ssl::flush_out send() failed: " << strerror(errno); throw n; } if (s == 0) { Loading Loading @@ -3523,38 +3605,35 @@ size_t netplus::ssl::sendData(buffer& data, int flags){ throw n; } const char* p = data.ptr ? data.data.ptr : data.data.buf; if (!p || data.size == 0) return 0; const char* p = data.ptr ? data.data.ptr : data.data.buf; // <-- VERIFY with your buffer layout if (!p || data.size == 0) return 0; static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = std::min<size_t>(data.size, TLS_MAX_PLAINTEXT); if (!_aes || _mac_key.empty()) throwSSL(NetException::Error, "cipher context missing"); if (!_aes) throwSSL(NetException::Error, "cipher context missing"); if (!_is_tls13 && _mac_key.empty()) throwSSL(NetException::Error, "MAC key missing for TLS 1.2"); std::vector<uint8_t> plain(take); std::memcpy(plain.data(), p, take); // Prefer avoiding copy if sendTLSRecord doesn't mutate std::vector<uint8_t> plain(p, p + take); // ApplicationData if (_is_tls13) _sendTLS13Record(0x17, plain); else _sendTLS12Record(0x17, plain); // flush ONCE try { flush_out(); } catch (NetException& e) { if (e.getErrorType() == NetException::Note) { // queued is already success if (e.getErrorType() == NetException::Note) return take; } throw; } return take; } size_t netplus::ssl::recvData(buffer& data, int flags) { auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -3569,7 +3648,9 @@ size_t netplus::ssl::recvData(buffer& data, int flags) throw n; } // 1) deliver leftover plaintext // ============================================================ // 1) deliver leftover plaintext (partial read from last record) // ============================================================ if (_recv_off < _rx_record_buf.size()) { const size_t avail = _rx_record_buf.size() - _recv_off; const size_t outLen = std::min<size_t>((size_t)data.size, avail); Loading @@ -3581,44 +3662,46 @@ size_t netplus::ssl::recvData(buffer& data, int flags) _rx_record_buf.clear(); _recv_off = 0; } data.size = outLen; return outLen; } // 2) read up to N records to find appdata // ============================================================ // 2) read records until we find application data // ============================================================ for (int attempts = 0; attempts < 4; ++attempts) { std::vector<uint8_t> rec; rec = readTlsRecordAsync(); // throws Note if not enough data std::vector<uint8_t> rec = readTlsRecordAsync(); // may throw Note if (rec.size() < 5) throwSSL(NetException::Error, "TLS record too short"); const uint8_t type = rec[0]; const uint8_t outer_type = rec[0]; const uint16_t ver = (uint16_t(rec[1]) << 8) | uint16_t(rec[2]); std::vector<uint8_t> frag(rec.begin() + 5, rec.end()); // ------------------------------------------------------------ // TLS 1.2 record handling (CBC in your code) // ------------------------------------------------------------ if (!_is_tls13) { // ALERT => orderly shutdown if (type == 0x15) { if (outer_type == 0x15) return 0; } // Not ApplicationData? => ignore and continue reading // (Handshake/CCS etc. can happen; do NOT throw Note) if (type != 0x17) { // ignore non-appdata records if (outer_type != 0x17) continue; } if (!_aes_recv) throwSSL(NetException::Error, "aes_recv missing"); std::vector<uint8_t> plain = _decryptRecordCBC(type, ver, frag); std::vector<uint8_t> plain = _decryptRecordCBC(outer_type, ver, frag); // Empty plaintext: just try next record if (plain.empty()) { if (plain.empty()) continue; } // store decrypted bytes for partial reads _rx_record_buf = std::move(plain); Loading @@ -3636,6 +3719,68 @@ size_t netplus::ssl::recvData(buffer& data, int flags) return outLen; } // ------------------------------------------------------------ // TLS 1.3 record handling (AEAD in your code) // ------------------------------------------------------------ else { // RFC8446: outer record_type MUST be 0x17 for protected records. // In practice you may still see alerts in clear during early stages, // but once handshakeDone, expect 0x17. // // If an outer alert arrives anyway => shutdown. if (outer_type == 0x15) return 0; // ignore non-appdata outer types (shouldn't happen in 1.3 after HS) if (outer_type != 0x17) continue; std::vector<uint8_t> inner_plain; uint8_t inner_type = 0; // For application traffic, handshake_keys=false. // (Handshake traffic would be processed elsewhere, not in recvData()) _tls13_recv_record( outer_type, ver, frag.data(), frag.size(), inner_plain, inner_type, /*handshake_keys=*/false ); // inner_type defines true content if (inner_type == 0x15) { // alert => orderly shutdown return 0; } // Not application data? ignore and keep reading. // (Handshake / NewSessionTicket / KeyUpdate, etc.) if (inner_type != 0x17) continue; if (inner_plain.empty()) continue; // store decrypted bytes for partial reads _rx_record_buf = std::move(inner_plain); _recv_off = 0; const size_t outLen = std::min<size_t>((size_t)data.size, _rx_record_buf.size()); std::memcpy(data.data.buf, _rx_record_buf.data(), outLen); _recv_off = outLen; data.size = outLen; if (_recv_off == _rx_record_buf.size()) { _rx_record_buf.clear(); _recv_off = 0; } return outLen; } } // no appdata found after several records NetException n; n[NetException::Note] << "ssl::recvData: no appdata yet"; Loading Loading @@ -4583,7 +4728,7 @@ bool netplus::ssl::handshakeStepIOCP() return; } case HsState::WAIT_CKE: { case HsState::: { if (ct != 0x16 || rec.size() < 11 || rec[5] != 0x10) { throwSSL(netplus::NetException::Error, "Expected ClientKeyExchange"); } Loading Loading
src/crypto/curve25519.cpp +54 −139 Original line number Diff line number Diff line Loading @@ -2,10 +2,6 @@ #include "curve25519.h" #ifndef NETPLUS_CURVE25519_DEBUG #define NETPLUS_CURVE25519_DEBUG 1 #endif namespace netplus { typedef int32_t fe[10]; Loading Loading @@ -873,92 +869,51 @@ void fe_frombytes(fe h, const unsigned char *s) { h[9] = (int32_t) h9; } void fe_invert(fe out, const fe z) { fe t0; fe t1; fe t2; fe t3; // out = z^(p-2) in GF(2^255-19) void fe_invert(fe out, const fe z) {/* fe t0, t1, t2, t3; int i; fe_sq(t0, z); for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_sq(t1, t0); for (i = 1; i < 2; ++i) { fe_sq(t1, t1); } fe_mul(t1, z, t1); fe_mul(t0, t0, t1); fe_sq(t2, t0); for (i = 1; i < 1; ++i) { fe_sq(t2, t2); } fe_mul(t1, t1, t2); fe_sq(t2, t1); fe_sq(t0, z); // 2 fe_sq(t1, t0); // 4 fe_sq(t1, t1); // 8 fe_mul(t1, z, t1); // 9 fe_mul(t0, t0, t1); // 11 fe_sq(t2, t0); // 22 fe_mul(t1, t1, t2); // 31 for (i = 1; i < 5; ++i) { fe_sq(t2, t2); } fe_sq(t2, t1); // 62 for (i = 1; i < 5; i++) fe_sq(t2, t2); // 2^5*31 = 992 fe_mul(t1, t2, t1); // 1023 = 2^10-1 fe_mul(t1, t2, t1); fe_sq(t2, t1); for (i = 1; i < 10; i++) fe_sq(t2, t2); fe_mul(t2, t2, t1); // 2^20-1 for (i = 1; i < 10; ++i) { fe_sq(t2, t2); } fe_mul(t2, t2, t1); fe_sq(t3, t2); for (i = 1; i < 20; i++) fe_sq(t3, t3); fe_mul(t2, t3, t2); // 2^40-1 for (i = 1; i < 20; ++i) { fe_sq(t3, t3); } fe_mul(t2, t3, t2); fe_sq(t2, t2); for (i = 1; i < 10; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^50-1 for (i = 1; i < 10; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t2, t1); for (i = 1; i < 50; i++) fe_sq(t2, t2); fe_mul(t2, t2, t1); // 2^100-1 for (i = 1; i < 50; ++i) { fe_sq(t2, t2); } fe_mul(t2, t2, t1); fe_sq(t3, t2); for (i = 1; i < 100; i++) fe_sq(t3, t3); fe_mul(t2, t3, t2); // 2^200-1 for (i = 1; i < 100; ++i) { fe_sq(t3, t3); } fe_mul(t2, t3, t2); fe_sq(t2, t2); for (i = 1; i < 50; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^250-1 for (i = 1; i < 50; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 5; ++i) { fe_sq(t1, t1); } fe_mul(out, t1, t0); for (i = 1; i < 5; i++) fe_sq(t1, t1); // 2^255-32 fe_mul(out, t1, t0); // 2^255-21 = p-2*/ fe_0(out); } void fe_tobytes(unsigned char *s, const fe h) { Loading Loading @@ -1130,7 +1085,7 @@ int fe_isnonzero(const fe f) { return r != 0; } void fe_mul121666(fe h, fe f) { void fe_mul121666(fe h, const fe f) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; Loading Loading @@ -1231,90 +1186,50 @@ void fe_neg(fe h, const fe f) { h[9] = h9; } // out = z^((p-5)/8) = z^(2^252 - 3) void fe_pow22523(fe out, const fe z) { fe t0; fe t1; fe t2; fe t0, t1, t2; int i; fe_sq(t0, z); for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_sq(t1, t0); for (i = 1; i < 2; ++i) { fe_sq(t1, t1); } fe_mul(t1, z, t1); fe_mul(t0, t0, t1); fe_sq(t0, t0); fe_sq(t0, z); // 2 fe_sq(t1, t0); // 4 fe_sq(t1, t1); // 8 fe_mul(t1, z, t1); // 9 fe_mul(t0, t0, t1); // 11 fe_sq(t0, t0); // 22 fe_mul(t0, t1, t0); // 31 for (i = 1; i < 1; ++i) { fe_sq(t0, t0); } fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 5; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^10-1 for (i = 1; i < 5; ++i) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 10; i++) fe_sq(t1, t1); fe_mul(t1, t1, t0); // 2^20-1 for (i = 1; i < 10; ++i) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); fe_sq(t2, t1); for (i = 1; i < 20; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^40-1 for (i = 1; i < 20; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 10; ++i) { fe_sq(t1, t1); } for (i = 1; i < 10; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^50-1 fe_mul(t0, t1, t0); fe_sq(t1, t0); for (i = 1; i < 50; i++) fe_sq(t1, t1); fe_mul(t1, t1, t0); // 2^100-1 for (i = 1; i < 50; ++i) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); fe_sq(t2, t1); for (i = 1; i < 100; i++) fe_sq(t2, t2); fe_mul(t1, t2, t1); // 2^200-1 for (i = 1; i < 100; ++i) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); fe_sq(t1, t1); for (i = 1; i < 50; i++) fe_sq(t1, t1); fe_mul(t0, t1, t0); // 2^250-1 for (i = 1; i < 50; ++i) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); fe_sq(t0, t0); for (i = 1; i < 2; ++i) { fe_sq(t0, t0); } fe_mul(out, t0, z); return; fe_sq(t0, t0); // 2^252-4 fe_mul(out, t0, z); // 2^252-3 } void fe_sub(fe h, const fe f, const fe g) { Loading
src/crypto/hkdf.cpp +26 −0 Original line number Diff line number Diff line #include "hkdf.h" #include <iostream> #include <iomanip> #include <stdexcept> #include <cstring> namespace netplus { static inline void dumpHex(const char* prefix, const uint8_t* p, size_t n, size_t max = 64) { std::ios oldState(nullptr); oldState.copyfmt(std::cerr); std::cerr << prefix << " (" << n << " bytes)"; if (n > max) std::cerr << " showing first " << max; std::cerr << ":\n "; size_t m = (n < max) ? n : max; for (size_t i = 0; i < m; ++i) { std::cerr << std::hex << std::setw(2) << std::setfill('0') << int(p[i]) << ' '; if ((i % 16) == 15 && i + 1 < m) std::cerr << "\n "; } std::cerr << std::dec << "\n"; std::cerr.copyfmt(oldState); } HKDF_SHA256::HKDF_SHA256(Sha256Func sha256, HmacFunc hmac) : _sha256(std::move(sha256)), _hmac(std::move(hmac)) { Loading Loading @@ -94,6 +114,12 @@ std::vector<uint8_t> HKDF_SHA256::expand_label(const std::vector<uint8_t>& secre info.push_back(uint8_t(context.size())); info.insert(info.end(), context.begin(), context.end()); auto out = expand(secret, info, outLen); dumpHex("HKDF secret", secret.data(), secret.size(), 64); dumpHex("HKDF info", info.data(), info.size(), 64); dumpHex("HKDF out", out.data(), out.size(), 64); return out; return expand(secret, info, outLen); } Loading
src/ssl.cpp +222 −77 Original line number Diff line number Diff line Loading @@ -472,7 +472,6 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( uint8_t type, uint16_t ver, const std::vector<uint8_t>& payload) { if (!_aes_recv) { NetException e; e[NetException::Error] << "ssl: decryptRecordCBC called but _aes_recv is NULL (no keys yet)"; Loading @@ -488,7 +487,6 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( throw e; }; // TLS 1.1+ explicit IV: must contain at least IV + 1 block if (payload.size() < 16 + block) throwSSL("record too short (need explicit IV + >=1 block)"); Loading @@ -499,10 +497,8 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( std::vector<uint8_t> iv(payload.begin(), payload.begin() + 16); std::vector<uint8_t> ct(payload.begin() + 16, payload.end()); // Decrypt CBC std::vector<uint8_t> p; try { // !!! IMPORTANT: your AES api is decryptCBC(ciphertext, iv) p = _aes_recv->decryptCBC(ct, iv); } catch (const std::exception& ex) { throwSSL(std::string("AES-CBC decrypt failed: ") + ex.what()); Loading @@ -514,11 +510,9 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( // Strip padding uint8_t padLen = p.back(); size_t padTotal = size_t(padLen) + 1; if (padTotal > p.size()) throwSSL("invalid padding length"); // TLS padding check: all padding bytes must equal padLen for (size_t i = 0; i < padTotal; ++i) { if (p[p.size() - 1 - i] != padLen) throwSSL("bad padding bytes"); Loading Loading @@ -547,17 +541,20 @@ std::vector<uint8_t> netplus::ssl::_decryptRecordCBC( macIn.insert(macIn.end(), hdr5.begin(), hdr5.end()); macIn.insert(macIn.end(), p.begin(), p.end()); std::vector<uint8_t> expected = _hmac_sha1(_client_mac_key, macIn); // 🔥 FIX: choose correct peer MAC key const std::vector<uint8_t>& mac_key = _is_client ? _mac_key : _client_mac_key; std::vector<uint8_t> expected = _hmac_sha1(mac_key, macIn); if (expected.size() != mac.size()) throwSSL("internal MAC length mismatch"); // constant-time compare would be better, but ok for now if (!std::equal(expected.begin(), expected.end(), mac.begin())) throwSSL("record MAC mismatch"); _recv_seq++; return p; // plaintext content bytes return p; } // ---- Encrypt+MAC outbound TLS1.2 AES-CBC record; increments _send_seq ---- Loading Loading @@ -1176,9 +1173,8 @@ std::vector<uint8_t> netplus::ssl::_fetchNextHandshakePlain() // -------------------------------------------------------- if (_is_tls13 && ct == 0x17) { // We must have handshake recv keys set if (!_aes13_hs_recv) throwSSL(netplus::NetException::Error, "TLS1.3 appdata record but hs recv key not set"); throwSSL(NetException::Error, "TLS1.3 appdata record but hs recv key not set"); std::vector<uint8_t> plain; uint8_t inner_type = 0; Loading @@ -1197,14 +1193,19 @@ std::vector<uint8_t> netplus::ssl::_fetchNextHandshakePlain() << std::hex << int(inner_type) << std::dec << " plain=" << plain.size() << " bytes\n"; if (inner_type != 0x16) { // could be alert or appdata; during handshake this is illegal throwSSL(netplus::NetException::Error, "TLS1.3 expected inner handshake"); if (inner_type == 0x16) { // ✅ THIS IS THE MISSING PART _rx_handshake_buf.insert(_rx_handshake_buf.end(), plain.begin(), plain.end()); continue; } continue; if (inner_type == 0x15) { // alert throwSSL(NetException::Error, "TLS1.3 alert received during handshake"); } throwSSL(NetException::Error, "TLS1.3 unexpected inner content type during handshake"); } // -------------------------------------------------------- // 7) Ignore ApplicationData during handshake? -> illegal here Loading Loading @@ -1739,9 +1740,21 @@ void netplus::ssl::handshake_after_accept(){ // compression uint8_t compLen = readU8(p); if (p + compLen > ch.size()) throwSSL(NetException::Error, "compression truncated"); if (compLen < 1) throwSSL(NetException::Error, "compression_methods empty"); if (p + compLen > ch.size()) throwSSL(NetException::Error, "compression_methods truncated"); // must contain 0x00 at least once bool hasNull = false; for (uint8_t i=0;i<compLen;i++) if (ch[p+i] == 0x00) hasNull = true; if (!hasNull) throwSSL(NetException::Error, "no null compression offered"); p += compLen; // defaults bool offeredTLS13 = false; bool secureReneg = false; Loading Loading @@ -1863,8 +1876,6 @@ void netplus::ssl::handshake_after_accept(){ // ✅ TLS 1.3 chosen only if we can actually do it auto pickTls13Suite = [&]() -> uint16_t { // preference order (you can reorder if you want) if (hasSuite(0x1303)) return 0x1303; // CHACHA20_POLY1305_SHA256 if (hasSuite(0x1302)) return 0x1302; // AES_256_GCM_SHA384 if (hasSuite(0x1301)) return 0x1301; // AES_128_GCM_SHA256 return 0; }; Loading @@ -1882,7 +1893,7 @@ void netplus::ssl::handshake_after_accept(){ _chosenSuite = tls13Suite; _secure_reneg = false; _hs_state = HsState::TLS13_SEND_SERVER_HELLO; return; continue; } Loading Loading @@ -2251,6 +2262,74 @@ void netplus::ssl::handshake_after_accept(){ return; } case HsState::WAIT_CKE: { // ✅ Always read full handshake message (handles fragmentation) std::vector<uint8_t> msg = _fetchNextHandshakePlain(); if (msg.empty()) return; // would block // msg = [type][len24][body...] if (msg.size() < 4) throwSSL(NetException::Error, "ClientKeyExchange too short"); uint8_t ht = msg[0]; uint32_t hlen = (uint32_t(msg[1]) << 16) | (uint32_t(msg[2]) << 8) | uint32_t(msg[3]); if (ht != 0x10) throwSSL(NetException::Error, "Expected ClientKeyExchange (0x10), got " + std::to_string(ht)); if (msg.size() != 4 + hlen) throwSSL(NetException::Error, "ClientKeyExchange length mismatch"); // RSA CKE body: uint16 encLen + ciphertext if (hlen < 2) throwSSL(NetException::Error, "ClientKeyExchange body too short"); size_t off = 4; uint16_t encLen = (uint16_t(msg[off]) << 8) | msg[off + 1]; off += 2; if (off + encLen > msg.size()) throwSSL(NetException::Error, "ClientKeyExchange truncated ciphertext"); const size_t kBytes = (_rsa.n.bitLength() + 7) / 8; if (encLen != kBytes) { throwSSL(NetException::Error, "RSA ciphertext length mismatch encLen=" + std::to_string(encLen) + " kBytes=" + std::to_string(kBytes)); } rsa::bigInt cipher = rsa::bigIntFromBytesBE(msg.data() + off, encLen); rsa::bigInt plainBI = _rsa.decrypt(cipher); std::vector<uint8_t> pkcs1 = rsa::bigIntToBytesBE(plainBI, kBytes); std::vector<uint8_t> preMaster = extractPreMasterFromPkcs1(pkcs1); // master_secret = PRF(PMS, "master secret", client_random || server_random) std::vector<uint8_t> msSeed = _clientRandom; msSeed.insert(msSeed.end(), _serverRandom.begin(), _serverRandom.end()); _masterSecret = _prf(preMaster, "master secret", msSeed, 48); // key_block = PRF(master, "key expansion", server_random || client_random) std::vector<uint8_t> kbSeed = _serverRandom; kbSeed.insert(kbSeed.end(), _clientRandom.begin(), _clientRandom.end()); std::vector<uint8_t> keyBlock = _prf(_masterSecret, "key expansion", kbSeed, 72); size_t k = 0; _client_mac_key.assign(keyBlock.begin() + k, keyBlock.begin() + k + 20); k += 20; _mac_key.assign(keyBlock.begin() + k, keyBlock.begin() + k + 20); k += 20; std::vector<uint8_t> clientKey(keyBlock.begin() + k, keyBlock.begin() + k + 16); k += 16; std::vector<uint8_t> serverKey(keyBlock.begin() + k, keyBlock.begin() + k + 16); _aes_recv = std::make_unique<aes>(clientKey); _aes = std::make_unique<aes>(serverKey); _hs_state = HsState::WAIT_CCS; return; } case HsState::TLS13_WAIT_CLIENT_FINISHED: { // wir erwarten encrypted records (outer 0x17) mit handshake keys Loading Loading @@ -2664,20 +2743,23 @@ void netplus::ssl::flush_out(){ } while (!_send_record.empty()) { const uint8_t* p = _send_record.data() + _send_off; size_t left = _send_record.size() - _send_off; buffer dat( reinterpret_cast<const char*>(_send_record.data() + _send_off), _send_record.size() - _send_off ) ; ssize_t s = ::send(fd(), p, left, 0); ssize_t s=0; if (s < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { try{ s = tcp::sendData(dat, 0); }catch(NetException &e){ NetException n; if(e.getErrorType()==NetException::Error){ n[NetException::Note] << "ssl::flush_out: would block"; throw n; } NetException e; e[NetException::Error] << "ssl::flush_out send() failed: " << strerror(errno); throw e; n[NetException::Error] << "ssl::flush_out send() failed: " << strerror(errno); throw n; } if (s == 0) { Loading Loading @@ -3523,38 +3605,35 @@ size_t netplus::ssl::sendData(buffer& data, int flags){ throw n; } const char* p = data.ptr ? data.data.ptr : data.data.buf; if (!p || data.size == 0) return 0; const char* p = data.ptr ? data.data.ptr : data.data.buf; // <-- VERIFY with your buffer layout if (!p || data.size == 0) return 0; static constexpr size_t TLS_MAX_PLAINTEXT = 16384; const size_t take = std::min<size_t>(data.size, TLS_MAX_PLAINTEXT); if (!_aes || _mac_key.empty()) throwSSL(NetException::Error, "cipher context missing"); if (!_aes) throwSSL(NetException::Error, "cipher context missing"); if (!_is_tls13 && _mac_key.empty()) throwSSL(NetException::Error, "MAC key missing for TLS 1.2"); std::vector<uint8_t> plain(take); std::memcpy(plain.data(), p, take); // Prefer avoiding copy if sendTLSRecord doesn't mutate std::vector<uint8_t> plain(p, p + take); // ApplicationData if (_is_tls13) _sendTLS13Record(0x17, plain); else _sendTLS12Record(0x17, plain); // flush ONCE try { flush_out(); } catch (NetException& e) { if (e.getErrorType() == NetException::Note) { // queued is already success if (e.getErrorType() == NetException::Note) return take; } throw; } return take; } size_t netplus::ssl::recvData(buffer& data, int flags) { auto throwSSL = [&](int etype, const std::string& msg) -> void { Loading @@ -3569,7 +3648,9 @@ size_t netplus::ssl::recvData(buffer& data, int flags) throw n; } // 1) deliver leftover plaintext // ============================================================ // 1) deliver leftover plaintext (partial read from last record) // ============================================================ if (_recv_off < _rx_record_buf.size()) { const size_t avail = _rx_record_buf.size() - _recv_off; const size_t outLen = std::min<size_t>((size_t)data.size, avail); Loading @@ -3581,44 +3662,46 @@ size_t netplus::ssl::recvData(buffer& data, int flags) _rx_record_buf.clear(); _recv_off = 0; } data.size = outLen; return outLen; } // 2) read up to N records to find appdata // ============================================================ // 2) read records until we find application data // ============================================================ for (int attempts = 0; attempts < 4; ++attempts) { std::vector<uint8_t> rec; rec = readTlsRecordAsync(); // throws Note if not enough data std::vector<uint8_t> rec = readTlsRecordAsync(); // may throw Note if (rec.size() < 5) throwSSL(NetException::Error, "TLS record too short"); const uint8_t type = rec[0]; const uint8_t outer_type = rec[0]; const uint16_t ver = (uint16_t(rec[1]) << 8) | uint16_t(rec[2]); std::vector<uint8_t> frag(rec.begin() + 5, rec.end()); // ------------------------------------------------------------ // TLS 1.2 record handling (CBC in your code) // ------------------------------------------------------------ if (!_is_tls13) { // ALERT => orderly shutdown if (type == 0x15) { if (outer_type == 0x15) return 0; } // Not ApplicationData? => ignore and continue reading // (Handshake/CCS etc. can happen; do NOT throw Note) if (type != 0x17) { // ignore non-appdata records if (outer_type != 0x17) continue; } if (!_aes_recv) throwSSL(NetException::Error, "aes_recv missing"); std::vector<uint8_t> plain = _decryptRecordCBC(type, ver, frag); std::vector<uint8_t> plain = _decryptRecordCBC(outer_type, ver, frag); // Empty plaintext: just try next record if (plain.empty()) { if (plain.empty()) continue; } // store decrypted bytes for partial reads _rx_record_buf = std::move(plain); Loading @@ -3636,6 +3719,68 @@ size_t netplus::ssl::recvData(buffer& data, int flags) return outLen; } // ------------------------------------------------------------ // TLS 1.3 record handling (AEAD in your code) // ------------------------------------------------------------ else { // RFC8446: outer record_type MUST be 0x17 for protected records. // In practice you may still see alerts in clear during early stages, // but once handshakeDone, expect 0x17. // // If an outer alert arrives anyway => shutdown. if (outer_type == 0x15) return 0; // ignore non-appdata outer types (shouldn't happen in 1.3 after HS) if (outer_type != 0x17) continue; std::vector<uint8_t> inner_plain; uint8_t inner_type = 0; // For application traffic, handshake_keys=false. // (Handshake traffic would be processed elsewhere, not in recvData()) _tls13_recv_record( outer_type, ver, frag.data(), frag.size(), inner_plain, inner_type, /*handshake_keys=*/false ); // inner_type defines true content if (inner_type == 0x15) { // alert => orderly shutdown return 0; } // Not application data? ignore and keep reading. // (Handshake / NewSessionTicket / KeyUpdate, etc.) if (inner_type != 0x17) continue; if (inner_plain.empty()) continue; // store decrypted bytes for partial reads _rx_record_buf = std::move(inner_plain); _recv_off = 0; const size_t outLen = std::min<size_t>((size_t)data.size, _rx_record_buf.size()); std::memcpy(data.data.buf, _rx_record_buf.data(), outLen); _recv_off = outLen; data.size = outLen; if (_recv_off == _rx_record_buf.size()) { _rx_record_buf.clear(); _recv_off = 0; } return outLen; } } // no appdata found after several records NetException n; n[NetException::Note] << "ssl::recvData: no appdata yet"; Loading Loading @@ -4583,7 +4728,7 @@ bool netplus::ssl::handshakeStepIOCP() return; } case HsState::WAIT_CKE: { case HsState::: { if (ct != 0x16 || rec.size() < 11 || rec[5] != 0x10) { throwSSL(netplus::NetException::Error, "Expected ClientKeyExchange"); } Loading