Commit 8aa41ebb authored by jan.koester's avatar jan.koester
Browse files

tls: fix TLS 1.2 server transcript and SHA-384 PRF

Server-side TLS 1.2 never completed a usable handshake:

- The ClientHello was only added to handshake_transcript on the TLS 1.3
  path, so the TLS 1.2 transcript started at ServerHello. With EMS the
  master secret came out wrong ("GCM tag verification failed" on the
  client Finished), without EMS the Finished check failed ("client
  Finished verify_data mismatch").

- The PRF and the handshake hash were always SHA-256. The *_SHA384 GCM
  suites (0xC02C, 0xC030) need SHA-384 for both (RFC 5289), and 0xC030
  is what the server prefers. This also broke the client against
  servers that pick ECDHE_ECDSA_AES_256_GCM_SHA384.

prf() is now a member that picks HMAC-SHA256/384 from chosenSuite, and
the TLS 1.2 transcript hashes go through tls12Hash().

Checked with openssl s_client/s_server: ECDHE-RSA-AES{128,256}-GCM,
AES{128,256}-SHA with and without EMS (server), ECDHE-ECDSA-AES{128,256}-GCM
(client), TLS 1.3 both ways.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent a2567cca
Loading
Loading
Loading
Loading
+24 −11
Changes for src/crypto/tls.cpp: 24 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -590,6 +590,10 @@ namespace netplus {
                if (chosenSuite == 0)
                    throwSSL(NetException::Error, "No supported cipher suite");

                // The ClientHello starts the transcript for TLS 1.2 too (EMS session
                // hash and both Finished messages, full and abbreviated handshake).
                handshake_transcript.insert(handshake_transcript.end(), ch.begin(), ch.end());

                // --- TLS 1.2 Session Resumption Check ---
                // If client sent a non-empty session_id and we have a cache,
                // look it up.  On hit the expensive RSA decrypt is skipped.
@@ -897,7 +901,7 @@ namespace netplus {
                        queueRaw(sccs, sizeof(sccs));
                        send_seq = 0;

                        std::vector<uint8_t> th = sha256_hash(handshake_transcript);
                        std::vector<uint8_t> th = tls12Hash(handshake_transcript);
                        std::vector<uint8_t> verifyServer = prf(masterSecret, "server finished", th, 12);

                        std::vector<uint8_t> sFin;
@@ -1112,7 +1116,7 @@ namespace netplus {
                    throwSSL(NetException::Error, "Bad Finished structure (client)");
                }

                std::vector<uint8_t> th = sha256_hash(handshake_transcript);
                std::vector<uint8_t> th = tls12Hash(handshake_transcript);
                std::vector<uint8_t> expectedClient = prf(masterSecret, "client finished", th, 12);

                if (!std::equal(expectedClient.begin(), expectedClient.end(), finPT.begin() + 4)) {
@@ -1143,7 +1147,7 @@ namespace netplus {
                    send_seq = 0; // ok - begin new crypto epoch after CCS

                    // ✅ Use transcript after receiving all handshake messages (including client Finished) for server Finished PRF
                    std::vector<uint8_t> th = sha256_hash(tls12_transcript_before_client_finished);
                    std::vector<uint8_t> th = tls12Hash(tls12_transcript_before_client_finished);
                    std::vector<uint8_t> verifyServer = prf(masterSecret, "server finished", th, 12);

                    std::vector<uint8_t> sFin;
@@ -1273,7 +1277,7 @@ namespace netplus {
                // master_secret derivation
                if (extended_master_secret) {
                    // RFC 7627: master_secret = PRF(PMS, "extended master secret", session_hash)
                    std::vector<uint8_t> sessionHash = sha256_hash(handshake_transcript);
                    std::vector<uint8_t> sessionHash = tls12Hash(handshake_transcript);
                    masterSecret = prf(preMaster, "extended master secret", sessionHash, 48);
                } else {
                    // RFC 5246: master_secret = PRF(PMS, "master secret", ClientRandom || ServerRandom)
@@ -2656,7 +2660,7 @@ namespace netplus {
                        // Derive master_secret (sendHandshake above already added CKE to transcript)
                        if (extended_master_secret) {
                            // RFC 7627: master_secret = PRF(PMS, "extended master secret", session_hash)
                            std::vector<uint8_t> sessionHash = sha256_hash(handshake_transcript);
                            std::vector<uint8_t> sessionHash = tls12Hash(handshake_transcript);
                            masterSecret = prf(preMaster, "extended master secret", sessionHash, 48);
                        } else {
                            std::vector<uint8_t> msSeed = clientRandom;
@@ -2733,7 +2737,7 @@ namespace netplus {

                        // 2) Compute client Finished verify_data
                        //    verify_data = PRF(master_secret, "client finished", Hash(transcript))
                        std::vector<uint8_t> th = sha256_hash(handshake_transcript);
                        std::vector<uint8_t> th = tls12Hash(handshake_transcript);
                        std::vector<uint8_t> verifyClient = prf(masterSecret, "client finished", th, 12);

                        // 3) Build Finished handshake message
@@ -2830,7 +2834,7 @@ namespace netplus {
                    }

                    // Verify server Finished: PRF(master_secret, "server finished", Hash(transcript))
                    std::vector<uint8_t> th = sha256_hash(handshake_transcript);
                    std::vector<uint8_t> th = tls12Hash(handshake_transcript);
                    std::vector<uint8_t> expectedServer = prf(masterSecret, "server finished", th, 12);

                    if (!std::equal(expectedServer.begin(), expectedServer.end(), finPT.begin() + 4)) {
@@ -2848,7 +2852,7 @@ namespace netplus {
                        queueRaw(ccs, sizeof(ccs));
                        send_seq = 0;

                        std::vector<uint8_t> cth = sha256_hash(handshake_transcript);
                        std::vector<uint8_t> cth = tls12Hash(handshake_transcript);
                        std::vector<uint8_t> verifyClient = prf(masterSecret, "client finished", cth, 12);

                        std::vector<uint8_t> finMsg;
@@ -3206,10 +3210,19 @@ namespace netplus {
        return sha1_hash(outerData);
    }

    bool tls::tls12UsesSha384() const {
        return chosenSuite == 0xC02C || chosenSuite == 0xC030;
    }

    std::vector<uint8_t> tls::tls12Hash(const std::vector<uint8_t>& transcript) const {
        return tls12UsesSha384() ? sha384_hash(transcript) : sha256_hash(transcript);
    }

    std::vector<uint8_t> tls::prf(const std::vector<uint8_t>& secret,
                                  const std::string& label,
                                  const std::vector<uint8_t>& seed,
                                  size_t outLen) {
                                  size_t outLen) const {
        auto hmac = tls12UsesSha384() ? hmac_sha384 : hmac_sha256;
        std::vector<uint8_t> labelSeed;
        labelSeed.insert(labelSeed.end(), label.begin(), label.end());
        labelSeed.insert(labelSeed.end(), seed.begin(), seed.end());
@@ -3218,12 +3231,12 @@ namespace netplus {
        std::vector<uint8_t> A = labelSeed;

        while (result.size() < outLen) {
            A = hmac_sha256(secret, A);
            A = hmac(secret, A);

            std::vector<uint8_t> stepInput = A;
            stepInput.insert(stepInput.end(), labelSeed.begin(), labelSeed.end());

            std::vector<uint8_t> stepHash = hmac_sha256(secret, stepInput);
            std::vector<uint8_t> stepHash = hmac(secret, stepInput);
            result.insert(result.end(), stepHash.begin(), stepHash.end());
        }

+9 −2
Changes for src/crypto/tls.h: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -638,12 +638,19 @@ namespace netplus {
         * @param outLen Desired output length in bytes.
         * @return @p outLen bytes of derived key material.
         */
        static std::vector<uint8_t> prf(
        std::vector<uint8_t> prf(
            const std::vector<uint8_t>& secret,
            const std::string& label,
            const std::vector<uint8_t>& seed,
            size_t outLen
        );
        ) const;

        /** @brief TLS 1.2: true when the negotiated suite's PRF hash is SHA-384
         *  (the *_SHA384 GCM suites, RFC 5289), false for SHA-256. */
        bool tls12UsesSha384() const;

        /** @brief TLS 1.2 handshake hash (session hash, Finished) with the suite's PRF hash. */
        std::vector<uint8_t> tls12Hash(const std::vector<uint8_t>& transcript) const;

        // HMAC Helpers