Skip to content

[Bug]: RFC 8446 violation : WolfSSL accept HelloRetryRequests with changing ciphers #9331

@aeyno

Description

@aeyno

Contact Details

No response

Version

5.8.2

Description

A WolfSSL TLS 1.3 client receiving a HelloRetryRequest followed by a ServerHello with another cipher (eg. using TLS_AES_256_GCM_SHA384 in HRR and TLS_AES_128_GCM_SHA256 in ServerHello), will continue the handshake while it should reject the ServerHello with an appropriate error.

According to the RFC 8446 section 4.1.4 : A client which receives a cipher suite that was not offered MUST abort the handshake. Servers MUST ensure that they negotiate the same cipher suite when receiving a conformant updated ClientHello (if the server selects the cipher suite as the first step in the negotiation, then this will happen automatically). Upon receiving the ServerHello, clients MUST check that the cipher suite supplied in the ServerHello is the same as that in the HelloRetryRequest and otherwise abort the handshake with an “illegal_parameter” alert. So the client should reject the ServerHello with the other cipher with an "illegal_parameter" alert.

Impact

Unknown, but we noticed that the transcript hash computed by WolfSSL is not using only the first (from HRR) or second (from SH) provided hash function, and we assume that the begining of the transcript (before SH) is computed using the first hash function and the rest is computed using the second hash function.

Expected behavior

WolfSSL client should reject the ServerHello, send an Alert message and abort the connection.

Reproduction steps

Here is an example of a TLS 1.3 handshake that triggers the described behavior :

  • Wait for ClientHello
  • Send the following HRR
    • Record Layer:
      • ContentType: Handshake
      • Version: TLS 1.2 (legacy marker)
      • Length: 56
    • Handshake:
      • Type: ServerHello
      • Length: 52
      • Version: 0x0303
      • Random: cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c (HelloRetryRequest magic value)
      • SessionID: empty
      • CipherSuite: TLS_AES_256_GCM_SHA384 (0x1301)
      • Compression: null
      • Extensions:
        • supported_versions = TLS 1.3 (0x0304)
        • KeyShare = secp384r1
          in raw hex : 1603030038020000340303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c00130200000c002b00020304003300020018
  • Wait for second ClientHello
  • Send the following ServerHello :
    • Record Layer:
      • ContentType: Handshake
      • Version: TLS 1.2 (legacy marker)
      • Length: 155
    • Handshake:
      • Type: ServerHello
      • Length: 151
      • Version: 0x0303
      • Random: 0101010101010101010101010101010101010101010101010101010101010101
      • SessionID: empty
      • CipherSuite: TLS_AES_128_GCM_SHA256 (0x1303)
      • Compression: null
      • Extensions:
        • KeyShare (len 101)
        • supported_versions = TLS 1.3 (0x0304)
          In raw hex : 160303009b020000970303010101010101010101010101010101010101010101010101010101010101010100130300006f003300650018006104533ee5bf40ec2d67988b77f317489bb6df952925c709fc0381111a5956f2d758110e59d3d7c1729e2c0d70eaf773e6120116426de2436a2f5fdd7fe54faf952b04fd13f516ce627f89d2019d4c8796959e4333c7065b496ca634d5dc63bde91f002b00020304

Start the following Python TCP server :

import socket

HOST = "0.0.0.0"
PORT = 3000

payload1 = bytes.fromhex(
    "1603030038020000340303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c00130200000c002b00020304003300020018"
)
payload2 = bytes.fromhex(
    "160303009b020000970303010101010101010101010101010101010101010101010101010101010101010100130300006f003300650018006104533ee5bf40ec2d67988b77f317489bb6df952925c709fc0381111a5956f2d758110e59d3d7c1729e2c0d70eaf773e6120116426de2436a2f5fdd7fe54faf952b04fd13f516ce627f89d2019d4c8796959e4333c7065b496ca634d5dc63bde91f002b00020304"
)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((HOST, PORT))
    server_socket.listen(1)
    print(f"[*] Listening on {HOST}:{PORT} ...")

    # Accept client connection
    conn, addr = server_socket.accept()
    with conn:
        print(f"[+] Connection from {addr}")

        # Wait for ClientHello
        data = conn.recv(1024)
        print(f"[>] Received: {data.hex()}")

        # Send HRR
        conn.sendall(payload1)
        print(f"[<] Sent: {payload1.hex()}")

        # Wait for second ClientHello
        data = conn.recv(1024)
        print(f"[>] Received: {data.hex()}")

        # Send ServerHello
        conn.sendall(payload2)
        print(f"[<] Sent: {payload2.hex()}")

        while True:
            data = conn.recv(1024)
            print(f"[>] Received: {data.hex()}")

Then start TLS 1.3 handshake with WolfSSL client :

./build/examples/client/client -p 3000 -v 4

You should see the WolfSSL client waiting for the server's EncryptedExtensions instead of aborting the connection.

Acknowledgements

This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin team:

  • Max Ammann
  • Olivier Demengeon - Loria, Inria
  • Tom Gouville - Loria, Inria
  • Lucca Hirschi - Loria, Inria
  • Steve Kremer - Loria, Inria
  • Michael Mera - Loria, Inria

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions