wolfssl icon indicating copy to clipboard operation
wolfssl copied to clipboard

[Bug]: RFC 8446 violation : WolfSSL server accept ClientHello with incorrect KeyShare after HelloRetryRequest

Open aeyno opened this issue 2 months ago • 8 comments

Contact Details

No response

Version

5.8.2

Description

A WolfSSL TLS 1.3 server sending a HelloRetryRequest requesting a specific KeyShare can accept a second ClientHello with a KeyShare not present in the HelloRetryRequest.

According to the RFC 8446 section 4.2.8 : when sending the new ClientHello, the client MUST replace the original "key_share" extension with one containing only a new KeyShareEntry for the group indicated in the selected_group field of the triggering HelloRetryRequest. So here the client is sending an incorrect KeyShare that should be rejected by the server.

Impact

Ability to do a TLS 1.3 handshake while ignoring the server KeyShare preference.

Expected behavior

WolfSSL server should send an "illegal_parameter" Alert and abort the connection.

Reproduction steps

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

  • Send first ClientHello TLSv1.3 Record Layer: Handshake Protocol: Client Hello
    • Content Type: Handshake (22)
    • Version: TLS 1.2 (0x0303)
    • Length: 150
    • Handshake Protocol: Client Hello
      • Handshake Type: Client Hello (1)
      • Length: 146
      • Version: TLS 1.2 (0x0303)
      • Random: 0101010101010101010101010101010101010101010101010101010101010101
      • Session ID Length: 32
      • Session ID: 0303030303030303030303030303030303030303030303030303030303030303
      • Cipher Suites Length: 2
      • Cipher Suites (1 suite)
      • Compression Methods Length: 1
      • Compression Methods (1 method)
      • Extensions Length: 71
      • Extension: supported_groups (len=8) -Type: supported_groups (10) -Length: 8 -Supported Groups List Length: 6 -Supported Groups (3 groups)
      • Extension: signature_algorithms (len=6)
        • Type: signature_algorithms (13)
        • Length: 6
        • Signature Hash Algorithms Length: 4
        • Signature Hash Algorithms (2 algorithms)
      • Extension: key_share (len=38) x25519
        • Type: key_share (51)
        • Length: 38
        • Key Share extension
          • Client Key Share Length: 36
          • Key Share Entry: Group: x25519, Key Exchange length: 32
            • Group: x25519 (29)
            • Key Exchange Length: 32
            • Key Exchange: 07aaff3e9fc167275544f4c3a6a17cd837f2ec6e78cd8a57b1e3dfb3cc035a76
      • Extension: supported_versions (len=3) TLS 1.3
        • Type: supported_versions (43)
        • Length: 3
        • Supported Versions length: 2
        • Supported Version: TLS 1.3 (0x0304) In raw hex : 160303009601000092030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000047000a0008000600180017001d000d0006000404010804003300260024001d002007aaff3e9fc167275544f4c3a6a17cd837f2ec6e78cd8a57b1e3dfb3cc035a76002b0003020304
  • Wait for HRR : with the default build of WolfSSL (when x25519 is not supported), the HelloRetryRequests asks for secp384r1 KeyShare
  • Send the second ClientHello with a secp256r1 KeyShare TLSv1.3 Record Layer: Handshake Protocol: Client Hello
    • Content Type: Handshake (22)
    • Version: TLS 1.2 (0x0303)
    • Length: 183
    • Handshake Protocol: Client Hello
      • Handshake Type: Client Hello (1)
      • Length: 179
      • Version: TLS 1.2 (0x0303)
      • Random: 0101010101010101010101010101010101010101010101010101010101010101
      • Session ID Length: 32
      • Session ID: 0303030303030303030303030303030303030303030303030303030303030303
      • Cipher Suites Length: 2
      • Cipher Suites (1 suite)
      • Compression Methods Length: 1
      • Compression Methods (1 method)
      • Extensions Length: 104
      • Extension: supported_groups (len=8)
        • Type: supported_groups (10)
        • Length: 8
        • Supported Groups List Length: 6
        • Supported Groups (3 groups)
      • Extension: signature_algorithms (len=6)
        • Type: signature_algorithms (13)
        • Length: 6
        • Signature Hash Algorithms Length: 4
        • Signature Hash Algorithms (2 algorithms)
      • Extension: key_share (len=71) secp256r1
        • Type: key_share (51)
        • Length: 71
        • Key Share extension
          • Client Key Share Length: 69
          • Key Share Entry: Group: secp256r1, Key Exchange length: 65
            • Group: secp256r1 (23)
            • Key Exchange Length: 65
            • Key Exchange: 040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569e
      • Extension: supported_versions (len=3) TLS 1.3
        • Type: supported_versions (43)
        • Length: 3
        • Supported Versions length: 2
        • Supported Version: TLS 1.3 (0x0304) In raw hex : 16030300b7010000b3030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000068000a0008000600180017001d000d000600040401080400330047004500170041040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569e002b0003020304
  • The server should send a ServerHello

Then start a TLS 1.3 WolfSSL server :

./examples/server/server -v 4 -l 'TLS_AES_128_GCM_SHA256'  -p 3000

Start the following Python TCP client :

import socket

HOST = "0.0.0.0"
PORT = 3000

payload1 = bytes.fromhex(
    "160303009601000092030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000047000a0008000600180017001d000d0006000404010804003300260024001d002007aaff3e9fc167275544f4c3a6a17cd837f2ec6e78cd8a57b1e3dfb3cc035a76002b0003020304"
)
payload2 = bytes.fromhex(
    "16030300b7010000b3030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000068000a0008000600180017001d000d000600040401080400330047004500170041040c901d423c831ca85e27c73c263ba132721bb9d7a84c4f0380b2a6756fd601331c8870234dec878504c174144fa4b14b66a651691606d8173e55bd37e381569e002b0003020304"
)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
    conn = client_socket.connect((HOST, PORT))
    print(f"[*] connecting to {HOST}:{PORT} ...")

    client_socket.sendall(payload1)
    print(f"[<] Sent: {payload1.hex()}")

    # receive HRR
    data = client_socket.recv(1024)
    print(f"[>] Received: {data.hex()}")

    # Send second client hello
    client_socket.sendall(payload2)
    print(f"[<] Sent: {payload2.hex()}")

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

You should see the WolfSSL server sending its ServerHello and encrypted extensions, waiting for the client to send a Finished message.

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

aeyno avatar Oct 29 '25 15:10 aeyno

Hi @aeyno,

Thank you for the in-depth bug report. I was able to confirm and reproduce this issue on 5.8.2 and master. I'm reviewing it with the team now.

kareem-wolfssl avatar Oct 29 '25 21:10 kareem-wolfssl

Hi @aeyno,

the RFC doesn't say that the server should respond with an alert or in any way puts the burden of checking this on the server. Our understanding is that the intended consequence is that if a client fails to adhere to the HRR KeyShare the server will deny the connection. Checking for conformity to HRR requirements breaks the stateless purpose of the HRR.

Juliusz

julek-wolfssl avatar Nov 04 '25 18:11 julek-wolfssl

Hi @julek-wolfssl, I do understand that HRR could be statelessl but for me the spec require the use of a Cookie. And WolfSSL HRR doesn't contain any TLS Cookie, thus I don't think WolfSSL's HRR is truely stateless. I also tested this scenario with OpenSSL, and they do reject the second ClientHello and abort the connection.

aeyno avatar Nov 07 '25 14:11 aeyno

Hi @aeyno,

please share how you ran this scenario against OpenSSL. I would like to inspect if they are saving the KeyShare sent or rejecting based on unsupported algos.

Juliusz

julek-wolfssl avatar Nov 07 '25 14:11 julek-wolfssl

Hi @julek-wolfssl , here is the OpenSSL server configuration that I use :

openssl s_server -tls1_3 -accept 3000 -cert cert.pem -key key.pem -state -ciphersuites "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256" -curves "P-256:P-384"

The Python script provided in the issue should result in OpenSSL sending an 'Illegal parameter' Alert.

I tested this with OpenSSL 3.6.0

aeyno avatar Nov 12 '25 13:11 aeyno

Hi @julek-wolfssl, do you recognize this as an RFC violation ? I saw you reopened the issue but kept the wontfix label ?

aeyno avatar Nov 24 '25 13:11 aeyno

Hi @aeyno I reopened the issue to investigate how OpenSSL handles this case. Unfortunately, I haven't had time yet to look into this. I will update this issue and the label once I have an answer for you.

Sincerely Juliusz

julek-wolfssl avatar Nov 24 '25 13:11 julek-wolfssl

Hi @aeyno, I've confirmed that OpenSSL does save the KeyShare sent in the HRR. I've changed this issue to "bug" and we will propose a fix that works for wolfSSL.

Sincerely Juliusz

julek-wolfssl avatar Dec 03 '25 13:12 julek-wolfssl

Hi @aeyno, please find a proposed fix at https://github.com/wolfSSL/wolfssl/pull/9544.

julek-wolfssl avatar Dec 17 '25 09:12 julek-wolfssl