Implement RFC 8701 GREASE for TLS ClientHello#30303
Conversation
|
Thank you very much for your feedback, @mattcaswell . I have done my best to incorporate and resubmit for your review. One small clarification question: |
mattcaswell
left a comment
There was a problem hiding this comment.
For grease1 (the empty extension), I kept WPACKET_put_bytes_u16(pkt, 0) since that matches the existing pattern for zero-length extensions (e.g. tls_construct_ctos_etm. Let me know if you'd prefer WPACKET_start_sub_packet_u16 / WPACKET_close there as well for consistency. Thanks.
You could go either way with that - but I'm fine with this.
|
The CI failures look relevant. |
|
Ping @openssl/committers for second review. |
t8m
left a comment
There was a problem hiding this comment.
Also please rebase this to resolve the conflict.
Add client-side GREASE (Generate Random Extensions And Sustain Extensibility) support per RFC 8701. When SSL_OP_GREASE is set, the TLS client injects reserved 0x?A?A-pattern values into the ClientHello to prevent ecosystem ossification caused by servers that reject unknown values. GREASE values are injected into: - Cipher suites (prepended) - Supported versions extension (prepended) - Supported groups extension (prepended) - Signature algorithms extension (appended) - Key share extension (prepended, 1 zero byte) - Two standalone extensions (one empty, one with 1 zero byte) The implementation uses lazy-seeded random values that remain consistent across HelloRetryRequest retransmissions. GREASE values from server responses are rejected as illegal parameters. Add -grease option to s_client to enable GREASE from the command line. Closes openssl#9660
Wrap the GREASE test functions and static variables in #if !defined(OSSL_NO_USABLE_TLS1_3) to match the existing guard on the ADD_TEST call. Fixes build failures with -Werror on no-tls, no-tls1_3, and minimal (no-bulk) configurations where TLS 1.3 is unavailable and the test functions were unused.
- Use WPACKET_start_sub_packet_u16/WPACKET_close instead of manually writing u16 length in key_share GREASE entry and GREASE extension 2 - Update CHANGES.md author attribution - Rewrite GREASE test to use PACKET functions (PACKET_get_net_2, PACKET_get_length_prefixed_*, etc.) instead of raw pointer arithmetic, following the pattern in clienthellotest.c - Replace magic numbers with SSL3_HM_HEADER_LENGTH, CLIENT_VERSION_LEN, and SSL3_RANDOM_SIZE macros - Expand test coverage to verify GREASE values in supported_groups, key_shares, signature_algorithms, and supported_versions in addition to cipher suites and extensions
- Initialize PACKET structs with memset to fix GCC -Werror=maybe-uninitialized errors on CI - Initialize ext_type and val variables to zero - Fix continuation line indentation to match clang-format - Expect exactly 2 GREASE extensions (grease1 and grease2) instead of >= 1
- Use RAND_bytes_ex() with libctx from SSL_CTX associated with the SSL_CONNECTION for GREASE seed generation, as requested by t8m. - Move GREASE CHANGES.md entry from "3.6 to 4.0" to "4.0 to 4.1" section, as requested by t8m.
- Reformat RAND_bytes_ex() call in ossl_grease_value() per clang-format - Fix || continuation indent in test_session_cache_overflow (upstream commit introduced a style violation picked up by our changed file)
|
This pull request is ready to merge |
|
conflicts resolved, squashed and merged to master, thank you |
Add client-side GREASE (Generate Random Extensions And Sustain Extensibility) support per RFC 8701. When SSL_OP_GREASE is set, the TLS client injects reserved 0x?A?A-pattern values into the ClientHello to prevent ecosystem ossification caused by servers that reject unknown values. GREASE values are injected into: - Cipher suites (prepended) - Supported versions extension (prepended) - Supported groups extension (prepended) - Signature algorithms extension (appended) - Key share extension (prepended, 1 zero byte) - Two standalone extensions (one empty, one with 1 zero byte) The implementation uses lazy-seeded random values that remain consistent across HelloRetryRequest retransmissions. GREASE values from server responses are rejected as illegal parameters. Add -grease option to s_client to enable GREASE from the command line. Closes #9660 Reviewed-by: Tomas Mraz <[email protected]> Reviewed-by: Matt Caswell <[email protected]> Reviewed-by: Neil Horman <[email protected]> MergeDate: Tue Mar 17 14:58:25 2026 (Merged from #30303)
|
Thanks for the contribution @mcrmck! |
Summary
Implements client-side RFC 8701 GREASE (Generate Random Extensions And Sustain Extensibility) for OpenSSL, addressing #9660 which has been open since 2019.
This picks up where #14560 left off, incorporating the review feedback from @davidben and @paulidale on that PR:
ossl_grease_value()is internal to libssl, not exported. The GREASE index constants are#defines inssl_local.h, not in public headers.#define, notenum.grease_seed[]lives inssl_connection_st.ext, not inSSL_SESSION, so GREASE values are not carried across resumptions.ossl_grease_value()lazy-seeds once viaRAND_bytesand returns the same value on repeated calls, ensuring consistency across HelloRetryRequest retransmissions.What is GREASE?
TLS endpoints that reject unknown values cause ecosystem ossification — new extensions and cipher suites can't be deployed because buggy implementations choke on them. GREASE prevents this by having clients routinely send reserved meaningless values (matching the
0x?A?Apattern) in ClientHello fields. Well-behaved servers ignore them; broken servers fail visibly.GREASE injection points
When
SSL_OP_GREASEis set on a client connection:The client also rejects GREASE values in server responses (ServerHello key_share group). Server-side tolerance already works —
tls_collect_extensions()ignores unknown extension types, and cipher/group/version negotiation naturally skips unknown values.Design decisions and differences from BoringSSL
This implementation closely follows BoringSSL's GREASE, which was written by the RFC author (@davidben). Key alignment:
grease_seed[]+ lazyRAND_bytesseeding patternOne deliberate difference: This implementation also GREASEs
signature_algorithms, which BoringSSL does not do. RFC 8701 Section 3.1 explicitly listsSignatureSchemevalues as a MAY field for GREASE. We include it for more thorough extensibility testing, but this could be removed if reviewers prefer strict alignment with BoringSSL.Files changed (13 files, +399/-2)
ssl/ssl_local.h— GREASE index defines, seed storage inssl_connection_st, helper declarationsinclude/openssl/ssl.h.in—SSL_OP_GREASEoption flag (bit 41)ssl/ssl_lib.c—ossl_grease_value()helper with lazy seeding and collision avoidancessl/ssl_ciph.c— GREASE cipher suite injection inssl_cipher_list_to_bytes()ssl/statem/extensions_clnt.c— GREASE in supported_versions, supported_groups, sig_algs, key_share; GREASE extension construct functions; GREASE rejection in stoc key_share parserssl/statem/extensions.c—ext_defs[]entries for GREASE extensionsssl/statem/statem_local.h— Sentinel type defines and function declarationsapps/s_client.c—-greaseCLI optiondoc/man1/openssl-s_client.pod.in— s_client documentationdoc/man3/SSL_CTX_set_options.pod—SSL_OP_GREASEdocumentationCHANGES.md— User-visible change notetest/ext_internal_test.c— Extension ordering test entriestest/sslapitest.c— GREASE functional test (msg_callback-based ClientHello parsing, handshake completion)Testing
make test TESTS=test_internal_exts— PASS (extension ordering)make test TESTS=test_sslapi— PASS (GREASE test verifies values in captured ClientHello + successful handshake)make test TESTS=test_ssl_new— PASS (no regressions)openssl s_client -grease -connect google.com:443completes TLS 1.3 handshake successfully