Loading src/ssl.cpp +92 −36 Original line number Diff line number Diff line Loading @@ -1508,8 +1508,15 @@ std::vector<uint8_t> netplus::ssl::_tls13_build_certificate() std::vector<uint8_t> netplus::ssl::_tls13_build_certificate_verify() { // transcript hash up to Certificate (inclusive!) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 // For 0x1301 (AES-128-GCM-SHA256), use SHA256 std::vector<uint8_t> th; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); // SHA384 for AES-256 } else { th = sha256_hash(_handshake_transcript); // SHA256 for AES-128 } std::vector<uint8_t> toSign(64, 0x20); const char* ctx = "TLS 1.3, server CertificateVerify"; Loading Loading @@ -1613,25 +1620,29 @@ static inline void dumpServerHelloStructure(const std::vector<uint8_t>&) {} void netplus::ssl::_tls13_derive_application_keys(){ // transcript hash up to server Finished included std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th; std::vector<uint8_t> empty_hash; size_t hash_size; // Determine hash size based on cipher suite // SHA-256 hash of empty string - needed for "derived" context per RFC 8446 std::vector<uint8_t> empty_hash = sha256_hash({}); if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); empty_hash = sha384_hash({}); hash_size = 48; // SHA384 } else { th = sha256_hash(_handshake_transcript); empty_hash = sha256_hash({}); hash_size = 32; // SHA256 } // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), 32) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's SHA256("") // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), hash_size) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's Hash("") std::vector<uint8_t> derived_secret = _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, 32); _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, hash_size); if (derived_secret.size() != 32) if (derived_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: derived_secret wrong size"); // Determine hash size based on cipher suite size_t hash_size = 32; // SHA256 if (_chosenSuite == 0x1302) { hash_size = 48; // SHA384 } // master_secret = HKDF-Extract(derived_secret, 0^hash_size) std::vector<uint8_t> zero(hash_size, 0x00); std::vector<uint8_t> master_secret = _hkdf_extract(derived_secret, zero); Loading Loading @@ -1706,24 +1717,39 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ if (!handshake_keys) throwSSL(NetException::Error, "TLS1.3 send_finished called with handshake_keys=false"); // 1) transcript hash bis jetzt (CH..CV) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); if (th.size() != 32) // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } if (th.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: transcript hash wrong size"); // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", 32) // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_s_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); // 3) verify_data = HMAC(finished_key, transcript_hash) std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); if (verify_data.size() != 32) std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } if (verify_data.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: verify_data wrong size"); // 4) Finished Handshake senden (type 0x14) Loading Loading @@ -2611,7 +2637,16 @@ void netplus::ssl::handshake_after_accept(){ // ⚠️ Save transcript hash BEFORE fetching client Finished // The client computes its Finished over CH..server_Finished (not including client Finished) std::vector<uint8_t> th_before_client_finished = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th_before_client_finished; size_t hash_len; if (_chosenSuite == 0x1302) { th_before_client_finished = sha384_hash(_handshake_transcript); hash_len = 48; } else { th_before_client_finished = sha256_hash(_handshake_transcript); hash_len = 32; } // ✅ FIXED: Read ENCRYPTED handshake record (not plaintext!) // After ServerHello, all handshake messages are encrypted with handshake keys Loading @@ -2633,23 +2668,28 @@ void netplus::ssl::handshake_after_accept(){ if (ht != 0x14) throwSSL(NetException::Error, "TLS1.3 expected Finished from client"); if (len != 32 || msg.size() != 4 + 32) if (len != hash_len || msg.size() != 4 + hash_len) throwSSL(NetException::Error, "TLS1.3 Finished verify_data wrong size"); std::vector<uint8_t> client_verify(msg.begin() + 4, msg.end()); // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", 32) // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_c_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); std::vector<uint8_t> expected = _hmac_sha256(finished_key, th_before_client_finished); if (expected.size() != 32) std::vector<uint8_t> expected; if (_chosenSuite == 0x1302) { expected = _hmac_sha384(finished_key, th_before_client_finished); } else { expected = _hmac_sha256(finished_key, th_before_client_finished); } if (expected.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: expected verify_data wrong size"); if (expected != client_verify) Loading @@ -2660,7 +2700,7 @@ void netplus::ssl::handshake_after_accept(){ client_finished_msg.push_back(0x14); // Finished type client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x20); // length = 32 client_finished_msg.push_back(uint8_t(hash_len)); // length = hash_len (32 or 48) client_finished_msg.insert(client_finished_msg.end(), client_verify.begin(), client_verify.end()); _handshake_transcript.insert(_handshake_transcript.end(), Loading Loading @@ -4827,15 +4867,31 @@ bool netplus::ssl::_tls13_recv_record( } std::vector<uint8_t> netplus::ssl::_tls13_build_server_finished() { std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } std::vector<uint8_t> empty; std::vector<uint8_t> finished_key = _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, 32); _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, hash_len); std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } // Return just the verify_data (32 bytes) // Return just the verify_data (32 or 48 bytes depending on cipher suite) // The handshake header (type + len24) is added by _tls13_send_handshake return verify_data; } Loading src/ssl_temp.cpp +105 −36 Original line number Diff line number Diff line Loading @@ -1387,7 +1387,15 @@ std::vector<uint8_t> netplus::ssl::_tls13_build_certificate() std::vector<uint8_t> netplus::ssl::_tls13_build_certificate_verify() { // transcript hash up to Certificate (inclusive!) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 // For 0x1301 (AES-128-GCM-SHA256), use SHA256 std::vector<uint8_t> th; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); // SHA384 for AES-256 } else { th = sha256_hash(_handshake_transcript); // SHA256 for AES-128 } std::vector<uint8_t> toSign(64, 0x20); const char* ctx = "TLS 1.3, server CertificateVerify"; Loading Loading @@ -1491,38 +1499,54 @@ static inline void dumpServerHelloStructure(const std::vector<uint8_t>&) {} void netplus::ssl::_tls13_derive_application_keys(){ // transcript hash up to server Finished included std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // SHA-256 hash of empty string - needed for "derived" context per RFC 8446 std::vector<uint8_t> empty_hash = sha256_hash({}); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th; std::vector<uint8_t> empty_hash; size_t hash_size; // Determine hash size based on cipher suite if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); empty_hash = sha384_hash({}); hash_size = 48; // SHA384 } else { th = sha256_hash(_handshake_transcript); empty_hash = sha256_hash({}); hash_size = 32; // SHA256 } // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), 32) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's SHA256("") // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", Hash(""), hash_size) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's Hash("") std::vector<uint8_t> derived_secret = _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, 32); _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, hash_size); if (derived_secret.size() != 32) if (derived_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: derived_secret wrong size"); // master_secret = HKDF-Extract(derived_secret, 0^32) std::vector<uint8_t> zero(32, 0x00); // master_secret = HKDF-Extract(derived_secret, 0^hash_size) std::vector<uint8_t> zero(hash_size, 0x00); std::vector<uint8_t> master_secret = _hkdf_extract(derived_secret, zero); if (master_secret.size() != 32) if (master_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: master_secret wrong size"); // server/client application traffic secrets _tls13_s_ap_secret = _hkdf_expand_label(master_secret, "s ap traffic", th, 32); _tls13_c_ap_secret = _hkdf_expand_label(master_secret, "c ap traffic", th, 32); _tls13_s_ap_secret = _hkdf_expand_label(master_secret, "s ap traffic", th, hash_size); _tls13_c_ap_secret = _hkdf_expand_label(master_secret, "c ap traffic", th, hash_size); // Determine key size based on cipher suite size_t key_size = 16; // default to AES-128 (16 bytes) if (_chosenSuite == 0x1302) { key_size = 32; // AES-256 (32 bytes) } auto derive_key_iv = [&](const std::vector<uint8_t>& secret, std::vector<uint8_t>& out_key, std::vector<uint8_t>& out_iv) { out_key = _hkdf_expand_label(secret, "key", {}, 16); out_key = _hkdf_expand_label(secret, "key", {}, key_size); out_iv = _hkdf_expand_label(secret, "iv", {}, 12); if (out_key.size() != 16 || out_iv.size() != 12) if (out_key.size() != key_size || out_iv.size() != 12) throwSSL(NetException::Error, "TLS1.3 derive: key/iv wrong size"); }; Loading Loading @@ -1561,24 +1585,39 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ if (!handshake_keys) throwSSL(NetException::Error, "TLS1.3 send_finished called with handshake_keys=false"); // 1) transcript hash bis jetzt (CH..CV) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); if (th.size() != 32) // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } if (th.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: transcript hash wrong size"); // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", 32) // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_s_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); // 3) verify_data = HMAC(finished_key, transcript_hash) std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); if (verify_data.size() != 32) std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } if (verify_data.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: verify_data wrong size"); // 4) Finished Handshake senden (type 0x14) Loading Loading @@ -2421,7 +2460,16 @@ void netplus::ssl::handshake_after_accept(){ // ⚠️ Save transcript hash BEFORE fetching client Finished // The client computes its Finished over CH..server_Finished (not including client Finished) std::vector<uint8_t> th_before_client_finished = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th_before_client_finished; size_t hash_len; if (_chosenSuite == 0x1302) { th_before_client_finished = sha384_hash(_handshake_transcript); hash_len = 48; } else { th_before_client_finished = sha256_hash(_handshake_transcript); hash_len = 32; } // ✅ FIXED: Read ENCRYPTED handshake record (not plaintext!) // After ServerHello, all handshake messages are encrypted with handshake keys Loading @@ -2443,23 +2491,28 @@ void netplus::ssl::handshake_after_accept(){ if (ht != 0x14) throwSSL(NetException::Error, "TLS1.3 expected Finished from client"); if (len != 32 || msg.size() != 4 + 32) if (len != hash_len || msg.size() != 4 + hash_len) throwSSL(NetException::Error, "TLS1.3 Finished verify_data wrong size"); std::vector<uint8_t> client_verify(msg.begin() + 4, msg.end()); // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", 32) // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_c_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); std::vector<uint8_t> expected = _hmac_sha256(finished_key, th_before_client_finished); if (expected.size() != 32) std::vector<uint8_t> expected; if (_chosenSuite == 0x1302) { expected = _hmac_sha384(finished_key, th_before_client_finished); } else { expected = _hmac_sha256(finished_key, th_before_client_finished); } if (expected.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: expected verify_data wrong size"); if (expected != client_verify) Loading @@ -2470,7 +2523,7 @@ void netplus::ssl::handshake_after_accept(){ client_finished_msg.push_back(0x14); // Finished type client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x20); // length = 32 client_finished_msg.push_back(uint8_t(hash_len)); // length = hash_len (32 or 48) client_finished_msg.insert(client_finished_msg.end(), client_verify.begin(), client_verify.end()); _handshake_transcript.insert(_handshake_transcript.end(), Loading Loading @@ -4618,15 +4671,31 @@ bool netplus::ssl::_tls13_recv_record( } std::vector<uint8_t> netplus::ssl::_tls13_build_server_finished() { std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } std::vector<uint8_t> empty; std::vector<uint8_t> finished_key = _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, 32); _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, hash_len); std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } // Return just the verify_data (32 bytes) // Return just the verify_data (32 or 48 bytes depending on cipher suite) // The handshake header (type + len24) is added by _tls13_send_handshake return verify_data; } Loading Loading
src/ssl.cpp +92 −36 Original line number Diff line number Diff line Loading @@ -1508,8 +1508,15 @@ std::vector<uint8_t> netplus::ssl::_tls13_build_certificate() std::vector<uint8_t> netplus::ssl::_tls13_build_certificate_verify() { // transcript hash up to Certificate (inclusive!) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 // For 0x1301 (AES-128-GCM-SHA256), use SHA256 std::vector<uint8_t> th; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); // SHA384 for AES-256 } else { th = sha256_hash(_handshake_transcript); // SHA256 for AES-128 } std::vector<uint8_t> toSign(64, 0x20); const char* ctx = "TLS 1.3, server CertificateVerify"; Loading Loading @@ -1613,25 +1620,29 @@ static inline void dumpServerHelloStructure(const std::vector<uint8_t>&) {} void netplus::ssl::_tls13_derive_application_keys(){ // transcript hash up to server Finished included std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th; std::vector<uint8_t> empty_hash; size_t hash_size; // Determine hash size based on cipher suite // SHA-256 hash of empty string - needed for "derived" context per RFC 8446 std::vector<uint8_t> empty_hash = sha256_hash({}); if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); empty_hash = sha384_hash({}); hash_size = 48; // SHA384 } else { th = sha256_hash(_handshake_transcript); empty_hash = sha256_hash({}); hash_size = 32; // SHA256 } // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), 32) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's SHA256("") // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), hash_size) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's Hash("") std::vector<uint8_t> derived_secret = _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, 32); _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, hash_size); if (derived_secret.size() != 32) if (derived_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: derived_secret wrong size"); // Determine hash size based on cipher suite size_t hash_size = 32; // SHA256 if (_chosenSuite == 0x1302) { hash_size = 48; // SHA384 } // master_secret = HKDF-Extract(derived_secret, 0^hash_size) std::vector<uint8_t> zero(hash_size, 0x00); std::vector<uint8_t> master_secret = _hkdf_extract(derived_secret, zero); Loading Loading @@ -1706,24 +1717,39 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ if (!handshake_keys) throwSSL(NetException::Error, "TLS1.3 send_finished called with handshake_keys=false"); // 1) transcript hash bis jetzt (CH..CV) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); if (th.size() != 32) // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } if (th.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: transcript hash wrong size"); // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", 32) // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_s_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); // 3) verify_data = HMAC(finished_key, transcript_hash) std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); if (verify_data.size() != 32) std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } if (verify_data.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: verify_data wrong size"); // 4) Finished Handshake senden (type 0x14) Loading Loading @@ -2611,7 +2637,16 @@ void netplus::ssl::handshake_after_accept(){ // ⚠️ Save transcript hash BEFORE fetching client Finished // The client computes its Finished over CH..server_Finished (not including client Finished) std::vector<uint8_t> th_before_client_finished = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th_before_client_finished; size_t hash_len; if (_chosenSuite == 0x1302) { th_before_client_finished = sha384_hash(_handshake_transcript); hash_len = 48; } else { th_before_client_finished = sha256_hash(_handshake_transcript); hash_len = 32; } // ✅ FIXED: Read ENCRYPTED handshake record (not plaintext!) // After ServerHello, all handshake messages are encrypted with handshake keys Loading @@ -2633,23 +2668,28 @@ void netplus::ssl::handshake_after_accept(){ if (ht != 0x14) throwSSL(NetException::Error, "TLS1.3 expected Finished from client"); if (len != 32 || msg.size() != 4 + 32) if (len != hash_len || msg.size() != 4 + hash_len) throwSSL(NetException::Error, "TLS1.3 Finished verify_data wrong size"); std::vector<uint8_t> client_verify(msg.begin() + 4, msg.end()); // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", 32) // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_c_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); std::vector<uint8_t> expected = _hmac_sha256(finished_key, th_before_client_finished); if (expected.size() != 32) std::vector<uint8_t> expected; if (_chosenSuite == 0x1302) { expected = _hmac_sha384(finished_key, th_before_client_finished); } else { expected = _hmac_sha256(finished_key, th_before_client_finished); } if (expected.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: expected verify_data wrong size"); if (expected != client_verify) Loading @@ -2660,7 +2700,7 @@ void netplus::ssl::handshake_after_accept(){ client_finished_msg.push_back(0x14); // Finished type client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x20); // length = 32 client_finished_msg.push_back(uint8_t(hash_len)); // length = hash_len (32 or 48) client_finished_msg.insert(client_finished_msg.end(), client_verify.begin(), client_verify.end()); _handshake_transcript.insert(_handshake_transcript.end(), Loading Loading @@ -4827,15 +4867,31 @@ bool netplus::ssl::_tls13_recv_record( } std::vector<uint8_t> netplus::ssl::_tls13_build_server_finished() { std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } std::vector<uint8_t> empty; std::vector<uint8_t> finished_key = _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, 32); _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, hash_len); std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } // Return just the verify_data (32 bytes) // Return just the verify_data (32 or 48 bytes depending on cipher suite) // The handshake header (type + len24) is added by _tls13_send_handshake return verify_data; } Loading
src/ssl_temp.cpp +105 −36 Original line number Diff line number Diff line Loading @@ -1387,7 +1387,15 @@ std::vector<uint8_t> netplus::ssl::_tls13_build_certificate() std::vector<uint8_t> netplus::ssl::_tls13_build_certificate_verify() { // transcript hash up to Certificate (inclusive!) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 // For 0x1301 (AES-128-GCM-SHA256), use SHA256 std::vector<uint8_t> th; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); // SHA384 for AES-256 } else { th = sha256_hash(_handshake_transcript); // SHA256 for AES-128 } std::vector<uint8_t> toSign(64, 0x20); const char* ctx = "TLS 1.3, server CertificateVerify"; Loading Loading @@ -1491,38 +1499,54 @@ static inline void dumpServerHelloStructure(const std::vector<uint8_t>&) {} void netplus::ssl::_tls13_derive_application_keys(){ // transcript hash up to server Finished included std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // SHA-256 hash of empty string - needed for "derived" context per RFC 8446 std::vector<uint8_t> empty_hash = sha256_hash({}); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th; std::vector<uint8_t> empty_hash; size_t hash_size; // Determine hash size based on cipher suite if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); empty_hash = sha384_hash({}); hash_size = 48; // SHA384 } else { th = sha256_hash(_handshake_transcript); empty_hash = sha256_hash({}); hash_size = 32; // SHA256 } // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", SHA256(""), 32) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's SHA256("") // derived_secret = HKDF-Expand-Label(handshake_secret, "derived", Hash(""), hash_size) // RFC 8446: Derive-Secret uses Transcript-Hash(Messages), for empty messages that's Hash("") std::vector<uint8_t> derived_secret = _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, 32); _hkdf_expand_label(_tls13_hs_secret, "derived", empty_hash, hash_size); if (derived_secret.size() != 32) if (derived_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: derived_secret wrong size"); // master_secret = HKDF-Extract(derived_secret, 0^32) std::vector<uint8_t> zero(32, 0x00); // master_secret = HKDF-Extract(derived_secret, 0^hash_size) std::vector<uint8_t> zero(hash_size, 0x00); std::vector<uint8_t> master_secret = _hkdf_extract(derived_secret, zero); if (master_secret.size() != 32) if (master_secret.size() != hash_size) throwSSL(NetException::Error, "TLS1.3 derive: master_secret wrong size"); // server/client application traffic secrets _tls13_s_ap_secret = _hkdf_expand_label(master_secret, "s ap traffic", th, 32); _tls13_c_ap_secret = _hkdf_expand_label(master_secret, "c ap traffic", th, 32); _tls13_s_ap_secret = _hkdf_expand_label(master_secret, "s ap traffic", th, hash_size); _tls13_c_ap_secret = _hkdf_expand_label(master_secret, "c ap traffic", th, hash_size); // Determine key size based on cipher suite size_t key_size = 16; // default to AES-128 (16 bytes) if (_chosenSuite == 0x1302) { key_size = 32; // AES-256 (32 bytes) } auto derive_key_iv = [&](const std::vector<uint8_t>& secret, std::vector<uint8_t>& out_key, std::vector<uint8_t>& out_iv) { out_key = _hkdf_expand_label(secret, "key", {}, 16); out_key = _hkdf_expand_label(secret, "key", {}, key_size); out_iv = _hkdf_expand_label(secret, "iv", {}, 12); if (out_key.size() != 16 || out_iv.size() != 12) if (out_key.size() != key_size || out_iv.size() != 12) throwSSL(NetException::Error, "TLS1.3 derive: key/iv wrong size"); }; Loading Loading @@ -1561,24 +1585,39 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ if (!handshake_keys) throwSSL(NetException::Error, "TLS1.3 send_finished called with handshake_keys=false"); // 1) transcript hash bis jetzt (CH..CV) std::vector<uint8_t> th = sha256_hash(_handshake_transcript); if (th.size() != 32) // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } if (th.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: transcript hash wrong size"); // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", 32) // 2) finished_key = HKDF-Expand-Label(s_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_s_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); // 3) verify_data = HMAC(finished_key, transcript_hash) std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); if (verify_data.size() != 32) std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } if (verify_data.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: verify_data wrong size"); // 4) Finished Handshake senden (type 0x14) Loading Loading @@ -2421,7 +2460,16 @@ void netplus::ssl::handshake_after_accept(){ // ⚠️ Save transcript hash BEFORE fetching client Finished // The client computes its Finished over CH..server_Finished (not including client Finished) std::vector<uint8_t> th_before_client_finished = sha256_hash(_handshake_transcript); // RFC 8446: Use cipher suite's hash function std::vector<uint8_t> th_before_client_finished; size_t hash_len; if (_chosenSuite == 0x1302) { th_before_client_finished = sha384_hash(_handshake_transcript); hash_len = 48; } else { th_before_client_finished = sha256_hash(_handshake_transcript); hash_len = 32; } // ✅ FIXED: Read ENCRYPTED handshake record (not plaintext!) // After ServerHello, all handshake messages are encrypted with handshake keys Loading @@ -2443,23 +2491,28 @@ void netplus::ssl::handshake_after_accept(){ if (ht != 0x14) throwSSL(NetException::Error, "TLS1.3 expected Finished from client"); if (len != 32 || msg.size() != 4 + 32) if (len != hash_len || msg.size() != 4 + hash_len) throwSSL(NetException::Error, "TLS1.3 Finished verify_data wrong size"); std::vector<uint8_t> client_verify(msg.begin() + 4, msg.end()); // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", 32) // finished_key = HKDF-Expand-Label(c_hs_secret, "finished", "", hash_len) std::vector<uint8_t> finished_key = _hkdf_expand_label( _tls13_c_hs_secret, "finished", std::vector<uint8_t>{}, 32 hash_len ); if (finished_key.size() != 32) if (finished_key.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: finished_key wrong size"); std::vector<uint8_t> expected = _hmac_sha256(finished_key, th_before_client_finished); if (expected.size() != 32) std::vector<uint8_t> expected; if (_chosenSuite == 0x1302) { expected = _hmac_sha384(finished_key, th_before_client_finished); } else { expected = _hmac_sha256(finished_key, th_before_client_finished); } if (expected.size() != hash_len) throwSSL(NetException::Error, "TLS1.3 Finished: expected verify_data wrong size"); if (expected != client_verify) Loading @@ -2470,7 +2523,7 @@ void netplus::ssl::handshake_after_accept(){ client_finished_msg.push_back(0x14); // Finished type client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x00); client_finished_msg.push_back(0x20); // length = 32 client_finished_msg.push_back(uint8_t(hash_len)); // length = hash_len (32 or 48) client_finished_msg.insert(client_finished_msg.end(), client_verify.begin(), client_verify.end()); _handshake_transcript.insert(_handshake_transcript.end(), Loading Loading @@ -4618,15 +4671,31 @@ bool netplus::ssl::_tls13_recv_record( } std::vector<uint8_t> netplus::ssl::_tls13_build_server_finished() { std::vector<uint8_t> th = sha256_hash(_handshake_transcript); // RFC 8446: Transcript hash and finished MAC must use the cipher suite's hash function // For 0x1302 (AES-256-GCM-SHA384), use SHA384 (48-byte output) // For 0x1301 (AES-128-GCM-SHA256), use SHA256 (32-byte output) std::vector<uint8_t> th; size_t hash_len; if (_chosenSuite == 0x1302) { th = sha384_hash(_handshake_transcript); hash_len = 48; } else { th = sha256_hash(_handshake_transcript); hash_len = 32; } std::vector<uint8_t> empty; std::vector<uint8_t> finished_key = _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, 32); _hkdf_expand_label(_tls13_s_hs_secret, "finished", empty, hash_len); std::vector<uint8_t> verify_data = _hmac_sha256(finished_key, th); std::vector<uint8_t> verify_data; if (_chosenSuite == 0x1302) { verify_data = _hmac_sha384(finished_key, th); } else { verify_data = _hmac_sha256(finished_key, th); } // Return just the verify_data (32 bytes) // Return just the verify_data (32 or 48 bytes depending on cipher suite) // The handshake header (type + len24) is added by _tls13_send_handshake return verify_data; } Loading