[Bug]: RFC 8446 violation : WolfSSL server accept ClientHello with incorrect KeyShare after HelloRetryRequest
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
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.
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
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.
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
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
Hi @julek-wolfssl, do you recognize this as an RFC violation ? I saw you reopened the issue but kept the wontfix label ?
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
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
Hi @aeyno, please find a proposed fix at https://github.com/wolfSSL/wolfssl/pull/9544.