From a1420a699d2589c2c524ea1f569747f6aaa738f3 Mon Sep 17 00:00:00 2001 From: mcrmck Date: Sat, 7 Mar 2026 21:51:17 -0500 Subject: [PATCH] Implement RFC 8701 GREASE for TLS ClientHello 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 Reviewed-by: Matt Caswell Reviewed-by: Neil Horman MergeDate: Tue Mar 17 14:58:25 2026 (Merged from https://github.com/openssl/openssl/pull/30303) --- CHANGES.md | 20 +++ apps/s_client.c | 8 ++ doc/man1/openssl-s_client.pod.in | 8 ++ doc/man3/SSL_CTX_set_options.pod | 10 ++ include/openssl/ssl.h.in | 3 + ssl/ssl_ciph.c | 11 ++ ssl/ssl_lib.c | 34 +++++ ssl/ssl_local.h | 20 +++ ssl/statem/extensions.c | 12 ++ ssl/statem/extensions_clnt.c | 102 +++++++++++++- ssl/statem/statem_local.h | 9 ++ test/ext_internal_test.c | 2 + test/sslapitest.c | 226 ++++++++++++++++++++++++++++++- 13 files changed, 462 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 88b6a0ebdec..90069f94d7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -61,6 +61,26 @@ OpenSSL Releases *Daniel Kubec* + * Added support for RFC 8701 GREASE (Generate Random Extensions And Sustain + Extensibility). When `SSL_OP_GREASE` is set, the TLS client injects + reserved GREASE values into cipher suites, supported versions, supported + groups, signature algorithms, key share, and extensions in the ClientHello + to prevent ecosystem ossification. The `openssl s_client` command gains a + `-grease` option to enable this. + + *William McCormack* + +### Changes between 3.6 and 4.0 [xx XXX xxxx] + + * Added support for RFC 8701 GREASE (Generate Random Extensions And Sustain + Extensibility). When `SSL_OP_GREASE` is set, the TLS client injects + reserved GREASE values into cipher suites, supported versions, supported + groups, signature algorithms, key share, and extensions in the ClientHello + to prevent ecosystem ossification. The `openssl s_client` command gains a + `-grease` option to enable this. + + *William McCormack* + ### Changes between 3.6 and 4.0 [xx XXX xxxx] * Added `-expected-rpks` option to the `openssl s_client` diff --git a/apps/s_client.c b/apps/s_client.c index b48b296966d..592e3da79f6 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -598,6 +598,7 @@ typedef enum OPTION_choice { OPT_S_ENUM, OPT_IGNORE_UNEXPECTED_EOF, OPT_FALLBACKSCSV, + OPT_GREASE, OPT_NOCMDS, OPT_ADV, OPT_PROXY, @@ -671,6 +672,7 @@ const OPTIONS s_client_options[] = { { "read_buf", OPT_READ_BUF, 'p', "Default read buffer size to be used for connections" }, { "fallback_scsv", OPT_FALLBACKSCSV, '-', "Send the fallback SCSV" }, + { "grease", OPT_GREASE, '-', "Send GREASE values in ClientHello (RFC 8701)" }, OPT_SECTION("Identity"), { "cert", OPT_CERT, '<', "Client certificate file to use" }, @@ -1031,6 +1033,7 @@ int s_client_main(int argc, char **argv) #endif int read_buf_len = 0; int fallback_scsv = 0; + int grease = 0; OPTION_CHOICE o; #ifndef OPENSSL_NO_DTLS int enable_timeouts = 0; @@ -1518,6 +1521,9 @@ int s_client_main(int argc, char **argv) case OPT_FALLBACKSCSV: fallback_scsv = 1; break; + case OPT_GREASE: + grease = 1; + break; case OPT_KEYFORM: if (!opt_format(opt_arg(), OPT_FMT_ANY, &key_format)) goto opthelp; @@ -2304,6 +2310,8 @@ int s_client_main(int argc, char **argv) if (fallback_scsv) SSL_set_mode(con, SSL_MODE_SEND_FALLBACK_SCSV); + if (grease) + SSL_set_options(con, SSL_OP_GREASE); if (!noservername && (servername != NULL || dane_tlsa_domain == NULL)) { if (servername == NULL) { diff --git a/doc/man1/openssl-s_client.pod.in b/doc/man1/openssl-s_client.pod.in index 635da52be78..3e53b4d9a0c 100644 --- a/doc/man1/openssl-s_client.pod.in +++ b/doc/man1/openssl-s_client.pod.in @@ -76,6 +76,7 @@ B B [B<-sctp>] [B<-sctp_label_bug>] [B<-fallback_scsv>] +[B<-grease>] [B<-async>] [B<-maxfraglen> I] [B<-max_send_frag>] @@ -573,6 +574,13 @@ available where OpenSSL has support for SCTP enabled. Send TLS_FALLBACK_SCSV in the ClientHello. +=item B<-grease> + +Send GREASE (Generate Random Extensions And Sustain Extensibility) values in +the ClientHello as defined in RFC 8701. This injects random reserved values +into cipher suites, supported groups, supported versions, signature algorithms, +key share, and extensions to prevent ecosystem ossification. + =item B<-async> Switch on asynchronous mode. Cryptographic operations will be performed diff --git a/doc/man3/SSL_CTX_set_options.pod b/doc/man3/SSL_CTX_set_options.pod index d9aecb280b1..ec66190d7a6 100644 --- a/doc/man3/SSL_CTX_set_options.pod +++ b/doc/man3/SSL_CTX_set_options.pod @@ -408,6 +408,16 @@ ECH. If set, TLS ClientHello messages emitted by the client will ignore the ECHConfig config_id chosen by the server and use a random octet. +=item SSL_OP_GREASE + +If set, TLS ClientHello messages will include GREASE (Generate Random +Extensions And Sustain Extensibility) values as defined in RFC 8701. This +injects random reserved values (matching the 0x?A?A pattern) into cipher +suites, supported groups, supported versions, signature algorithms, key share +entries, and extensions. GREASE values help prevent ecosystem ossification by +ensuring servers and middleboxes tolerate unknown values. The injected values +are consistent across HelloRetryRequest replays within the same connection. + =back The following options no longer have any effect but their identifiers are diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index d7915db1aa5..cba2cd80ba6 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -453,6 +453,9 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg); #define SSL_OP_ECH_GREASE_RETRY_CONFIG SSL_OP_BIT(40) #endif +/* RFC 8701: Send GREASE values in ClientHello */ +#define SSL_OP_GREASE SSL_OP_BIT(41) + /* * Option "collections." */ diff --git a/ssl/ssl_ciph.c b/ssl/ssl_ciph.c index d0e95f0011b..fc12efaae1a 100644 --- a/ssl/ssl_ciph.c +++ b/ssl/ssl_ciph.c @@ -2261,6 +2261,17 @@ int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, if (s->mode & SSL_MODE_SEND_FALLBACK_SCSV) maxlen -= 2; + /* RFC 8701: prepend a GREASE cipher suite value */ + if ((s->options & SSL_OP_GREASE) && !s->server) { + uint16_t grease_cs = ossl_grease_value(s, OSSL_GREASE_CIPHER); + + if (!WPACKET_put_bytes_u16(pkt, grease_cs)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + totlen += 2; + } + for (i = 0; i < sk_SSL_CIPHER_num(sk) && totlen < maxlen; i++) { const SSL_CIPHER *c; diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index b3de7a0982e..be5f4a20f02 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -8496,3 +8496,37 @@ int SSL_CTX_get0_server_cert_type(const SSL_CTX *ctx, unsigned char **t, size_t *len = ctx->server_cert_type_len; return 1; } + +/* + * RFC 8701 GREASE - returns a GREASE value (0x?A?A pattern) for the given + * index. Seeds are generated lazily on first use and remain stable for the + * lifetime of the connection so that HelloRetryRequest replays get identical + * values. + */ +uint16_t ossl_grease_value(SSL_CONNECTION *s, int index) +{ + uint16_t ret; + + if (index < 0 || index > OSSL_GREASE_LAST_INDEX) + return 0x0A0A; + + if (!s->ext.grease_seeded) { + if (RAND_bytes_ex(SSL_CONNECTION_GET_CTX(s)->libctx, + s->ext.grease_seed, + sizeof(s->ext.grease_seed), 0) + <= 0) + memset(s->ext.grease_seed, 0x42, sizeof(s->ext.grease_seed)); + s->ext.grease_seeded = 1; + } + + /* Map seed byte to 0x?A?A pattern */ + ret = (s->ext.grease_seed[index] & 0xf0) | 0x0a; + ret |= ret << 8; + + /* Ensure EXT2 differs from EXT1 */ + if (index == OSSL_GREASE_EXT2 + && ret == ossl_grease_value(s, OSSL_GREASE_EXT1)) + ret ^= 0x1010; + + return ret; +} diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index e28cc2aa229..a7159d3c190 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -697,12 +697,23 @@ typedef enum tlsext_index_en { TLSEXT_IDX_certificate_authorities, TLSEXT_IDX_ech, TLSEXT_IDX_outer_extensions, + TLSEXT_IDX_grease1, + TLSEXT_IDX_grease2, TLSEXT_IDX_padding, TLSEXT_IDX_psk, /* Dummy index - must always be the last entry */ TLSEXT_IDX_num_builtins } TLSEXT_INDEX; +/* RFC 8701 GREASE seed indices */ +#define OSSL_GREASE_CIPHER 0 +#define OSSL_GREASE_GROUP 1 +#define OSSL_GREASE_EXT1 2 +#define OSSL_GREASE_EXT2 3 +#define OSSL_GREASE_VERSION 4 +#define OSSL_GREASE_SIGALG 5 +#define OSSL_GREASE_LAST_INDEX 5 + DEFINE_LHASH_OF_EX(SSL_SESSION); /* Needed in ssl_cert.c */ DEFINE_LHASH_OF_EX(X509_NAME); @@ -1741,6 +1752,10 @@ struct ssl_connection_st { #ifndef OPENSSL_NO_ECH OSSL_ECH_CONN ech; #endif + + /* RFC 8701 GREASE */ + uint8_t grease_seed[OSSL_GREASE_LAST_INDEX + 1]; + int grease_seeded; } ext; /* @@ -2505,6 +2520,11 @@ void ssl_sort_cipher_list(void); int ssl_load_ciphers(SSL_CTX *ctx); int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, WPACKET *pkt); +uint16_t ossl_grease_value(SSL_CONNECTION *s, int index); +static ossl_inline int ossl_is_grease_value(uint16_t val) +{ + return (val & 0x0f0f) == 0x0a0a && (val >> 8) == (val & 0xff); +} __owur int ssl_setup_sigalgs(SSL_CTX *ctx); int ssl_load_groups(SSL_CTX *ctx); int ssl_load_sigalgs(SSL_CTX *ctx); diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c index 16467f782fc..417255241c6 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c @@ -464,6 +464,18 @@ static const EXTENSION_DEFINITION ext_defs[] = { INVALID_EXTENSION, INVALID_EXTENSION, #endif /* END_OPENSSL_NO_ECH */ + { /* RFC 8701 GREASE extension 1 - type is dynamic */ + TLSEXT_TYPE_grease1, + SSL_EXT_CLIENT_HELLO, + 0, + NULL, + NULL, NULL, NULL, tls_construct_ctos_grease1, NULL }, + { /* RFC 8701 GREASE extension 2 - type is dynamic */ + TLSEXT_TYPE_grease2, + SSL_EXT_CLIENT_HELLO, + 0, + NULL, + NULL, NULL, NULL, tls_construct_ctos_grease2, NULL }, { /* Must be immediately before pre_shared_key */ TLSEXT_TYPE_padding, SSL_EXT_CLIENT_HELLO, diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c index 7cdb594e8e1..b2a5de6a873 100644 --- a/ssl/statem/extensions_clnt.c +++ b/ssl/statem/extensions_clnt.c @@ -335,6 +335,14 @@ EXT_RETURN tls_construct_ctos_supported_groups(SSL_CONNECTION *s, WPACKET *pkt, SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return EXT_RETURN_FAIL; } + /* RFC 8701: prepend a GREASE group value */ + if ((s->options & SSL_OP_GREASE) && !s->server) { + if (!WPACKET_put_bytes_u16(pkt, + ossl_grease_value(s, OSSL_GREASE_GROUP))) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + } /* Copy group ID if supported */ for (i = 0; i < num_groups; i++) { const TLS_GROUP_INFO *ginfo = NULL; @@ -452,8 +460,19 @@ EXT_RETURN tls_construct_ctos_sig_algs(SSL_CONNECTION *s, WPACKET *pkt, || !WPACKET_start_sub_packet_u16(pkt) /* Sub-packet for the actual list */ || !WPACKET_start_sub_packet_u16(pkt) - || !tls12_copy_sigalgs(s, pkt, salg, salglen) - || !WPACKET_close(pkt) + || !tls12_copy_sigalgs(s, pkt, salg, salglen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + /* RFC 8701: append a GREASE signature algorithm value */ + if ((s->options & SSL_OP_GREASE) && !s->server) { + if (!WPACKET_put_bytes_u16(pkt, + ossl_grease_value(s, OSSL_GREASE_SIGALG))) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + } + if (!WPACKET_close(pkt) || !WPACKET_close(pkt)) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return EXT_RETURN_FAIL; @@ -740,6 +759,14 @@ EXT_RETURN tls_construct_ctos_supported_versions(SSL_CONNECTION *s, WPACKET *pkt return EXT_RETURN_FAIL; } + /* RFC 8701: prepend a GREASE version value */ + if ((s->options & SSL_OP_GREASE) && !s->server) { + if (!WPACKET_put_bytes_u16(pkt, + ossl_grease_value(s, OSSL_GREASE_VERSION))) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + } for (currv = max_version; currv >= min_version; currv--) { if (!WPACKET_put_bytes_u16(pkt, currv)) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); @@ -873,6 +900,19 @@ EXT_RETURN tls_construct_ctos_key_share(SSL_CONNECTION *s, WPACKET *pkt, return EXT_RETURN_FAIL; } + /* RFC 8701: prepend a GREASE key share entry (1 byte of 0x00) */ + if ((s->options & SSL_OP_GREASE) && !s->server) { + uint16_t grease_group = ossl_grease_value(s, OSSL_GREASE_GROUP); + + if (!WPACKET_put_bytes_u16(pkt, grease_group) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, 0) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + } + tls1_get_requested_keyshare_groups(s, &pgroups, &num_groups); if (num_groups == 1 && pgroups[0] == 0) { /* Indication that no * prefix was used */ tls1_get_supported_groups(s, &pgroups, &num_groups); @@ -2174,6 +2214,12 @@ int tls_parse_stoc_key_share(SSL_CONNECTION *s, PACKET *pkt, return 0; } + /* RFC 8701: reject GREASE values selected by the server */ + if (ossl_is_grease_value(group_id)) { + SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_BAD_KEY_SHARE); + return 0; + } + if ((context & SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST) != 0) { const uint16_t *pgroups = NULL; size_t num_groups; @@ -2818,3 +2864,55 @@ int tls_parse_stoc_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context, return 1; } #endif /* END_OPENSSL_NO_ECH */ + +/* + * RFC 8701 GREASE extension constructors. Each writes an empty extension + * whose type is a GREASE value (0x?A?A pattern). + */ +EXT_RETURN tls_construct_ctos_grease1(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx) +{ + uint16_t grease_type; + + if (!(s->options & SSL_OP_GREASE) || s->server) + return EXT_RETURN_NOT_SENT; + + grease_type = ossl_grease_value(s, OSSL_GREASE_EXT1); + + if (!WPACKET_put_bytes_u16(pkt, grease_type) + || !WPACKET_put_bytes_u16(pkt, 0)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + + return EXT_RETURN_SENT; +} + +EXT_RETURN tls_construct_ctos_grease2(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx) +{ + uint16_t grease_type; + + if (!(s->options & SSL_OP_GREASE) || s->server) + return EXT_RETURN_NOT_SENT; + + grease_type = ossl_grease_value(s, OSSL_GREASE_EXT2); + + /* + * RFC 8701 recommends "varying length and contents" for GREASE + * extensions. Extension 1 is empty; extension 2 carries one zero byte + * so that servers are tested against both empty and non-empty unknown + * extensions. This mirrors the BoringSSL behaviour. + */ + if (!WPACKET_put_bytes_u16(pkt, grease_type) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, 0) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + + return EXT_RETURN_SENT; +} diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h index 0c3cd3ac08c..03e15f888c4 100644 --- a/ssl/statem/statem_local.h +++ b/ssl/statem/statem_local.h @@ -43,6 +43,9 @@ /* Invalid extension ID for non-supported extensions */ #define TLSEXT_TYPE_invalid 0x10000 #define TLSEXT_TYPE_out_of_range 0x10001 +/* RFC 8701 GREASE extension placeholders (actual type is dynamic) */ +#define TLSEXT_TYPE_grease1 0x10002 +#define TLSEXT_TYPE_grease2 0x10003 unsigned int ossl_get_extension_type(size_t idx); extern const unsigned char hrrrandom[]; @@ -478,6 +481,12 @@ EXT_RETURN tls_construct_ctos_psk_kex_modes(SSL_CONNECTION *s, WPACKET *pkt, EXT_RETURN tls_construct_ctos_cookie(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx); +EXT_RETURN tls_construct_ctos_grease1(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx); +EXT_RETURN tls_construct_ctos_grease2(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx); EXT_RETURN tls_construct_ctos_padding(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx); diff --git a/test/ext_internal_test.c b/test/ext_internal_test.c index 6c1aaed9f1d..c88f026dee0 100644 --- a/test/ext_internal_test.c +++ b/test/ext_internal_test.c @@ -79,6 +79,8 @@ static EXT_LIST ext_list[] = { EXT_EXCEPTION(ech), EXT_EXCEPTION(outer_extensions), #endif + EXT_ENTRY(grease1), + EXT_ENTRY(grease2), EXT_ENTRY(padding), EXT_ENTRY(psk), EXT_END(num_builtins) diff --git a/test/sslapitest.c b/test/sslapitest.c index 622e2a84699..de60ed8ada5 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -10308,7 +10308,7 @@ static int test_session_cache_overflow(int idx) * would free the get_sess_val, causing a use-after-free error. */ if (!TEST_true(CRYPTO_GET_REF(&get_sess_val->references, &references)) - || !TEST_int_ge(references, 2)) + || !TEST_int_ge(references, 2)) goto end; sess = SSL_get1_session(clientssl); if (!TEST_ptr(sess)) @@ -14144,6 +14144,227 @@ err: } #endif +#if !defined(OSSL_NO_USABLE_TLS1_3) +/* + * RFC 8701 GREASE test helpers. + * We capture the raw ClientHello via msg_callback and then parse it with + * PACKET functions to confirm that GREASE values (matching 0x?A?A) are + * present in the expected fields. + */ +static unsigned char *grease_ch_buf; +static size_t grease_ch_len; + +static int is_grease(unsigned int v) +{ + return (v & 0x0f0f) == 0x0a0a && (v >> 8) == (v & 0xff); +} + +static void grease_msg_cb(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + const unsigned char *p = buf; + + /* + * We want the outgoing (write_p == 1) handshake (content_type == 22) + * ClientHello (msg_type == 1). The buf starts at the handshake header: + * byte 0: msg_type, bytes 1-3: length + */ + if (write_p != 1 || content_type != SSL3_RT_HANDSHAKE + || len < SSL3_HM_HEADER_LENGTH + || p[0] != SSL3_MT_CLIENT_HELLO) + return; + + /* Only capture the first ClientHello (not HRR retry) */ + if (grease_ch_buf != NULL) + return; + + grease_ch_buf = OPENSSL_memdup(buf, len); + grease_ch_len = len; +} + +/* + * Parse a captured ClientHello (starting from handshake header) and check + * that it contains GREASE values in cipher suites, extensions, supported + * groups, key shares, and signature algorithms. + * Returns 1 on success, 0 on failure. + */ +static int check_grease_in_client_hello(void) +{ + PACKET pkt, ciphers, session, compression, exts, ext_data; + PACKET inner; + unsigned int ext_type = 0, val = 0; + int found_grease_cipher = 0; + int found_grease_ext = 0; + int found_grease_group = 0; + int found_grease_kshare = 0; + int found_grease_sigalg = 0; + int found_grease_version = 0; + + memset(&pkt, 0, sizeof(pkt)); + memset(&ciphers, 0, sizeof(ciphers)); + memset(&session, 0, sizeof(session)); + memset(&compression, 0, sizeof(compression)); + memset(&exts, 0, sizeof(exts)); + memset(&ext_data, 0, sizeof(ext_data)); + memset(&inner, 0, sizeof(inner)); + + if (!TEST_ptr(grease_ch_buf) + || !TEST_true(PACKET_buf_init(&pkt, grease_ch_buf, + grease_ch_len)) + /* Skip handshake message header */ + || !TEST_true(PACKET_forward(&pkt, SSL3_HM_HEADER_LENGTH)) + /* Skip client_version + random */ + || !TEST_true(PACKET_forward(&pkt, + CLIENT_VERSION_LEN + SSL3_RANDOM_SIZE)) + /* Skip session_id */ + || !TEST_true(PACKET_get_length_prefixed_1(&pkt, &session)) + /* Get cipher suites */ + || !TEST_true(PACKET_get_length_prefixed_2(&pkt, &ciphers)) + /* Skip compression */ + || !TEST_true(PACKET_get_length_prefixed_1(&pkt, &compression)) + /* Get extensions */ + || !TEST_true(PACKET_as_length_prefixed_2(&pkt, &exts))) + return 0; + + /* Scan cipher suites for GREASE */ + while (PACKET_remaining(&ciphers) > 0) { + if (!TEST_true(PACKET_get_net_2(&ciphers, &val))) + return 0; + if (is_grease(val)) + found_grease_cipher = 1; + } + + /* Scan extensions */ + while (PACKET_remaining(&exts) > 0) { + if (!TEST_true(PACKET_get_net_2(&exts, &ext_type)) + || !TEST_true(PACKET_get_length_prefixed_2(&exts, + &ext_data))) + return 0; + + if (is_grease(ext_type)) + found_grease_ext++; + + /* Check for GREASE inside supported_versions */ + if (ext_type == TLSEXT_TYPE_supported_versions) { + if (!TEST_true(PACKET_get_length_prefixed_1(&ext_data, + &inner))) + return 0; + while (PACKET_remaining(&inner) > 0) { + if (!TEST_true(PACKET_get_net_2(&inner, &val))) + return 0; + if (is_grease(val)) + found_grease_version = 1; + } + } + + /* Check for GREASE inside supported_groups */ + if (ext_type == TLSEXT_TYPE_supported_groups) { + if (!TEST_true(PACKET_get_length_prefixed_2(&ext_data, + &inner))) + return 0; + while (PACKET_remaining(&inner) > 0) { + if (!TEST_true(PACKET_get_net_2(&inner, &val))) + return 0; + if (is_grease(val)) + found_grease_group = 1; + } + } + + /* Check for GREASE inside key_share */ + if (ext_type == TLSEXT_TYPE_key_share) { + PACKET ks_entry; + + memset(&ks_entry, 0, sizeof(ks_entry)); + if (!TEST_true(PACKET_get_length_prefixed_2(&ext_data, + &inner))) + return 0; + while (PACKET_remaining(&inner) > 0) { + if (!TEST_true(PACKET_get_net_2(&inner, &val)) + || !TEST_true(PACKET_get_length_prefixed_2( + &inner, &ks_entry))) + return 0; + if (is_grease(val)) + found_grease_kshare = 1; + } + } + + /* Check for GREASE inside signature_algorithms */ + if (ext_type == TLSEXT_TYPE_signature_algorithms) { + if (!TEST_true(PACKET_get_length_prefixed_2(&ext_data, + &inner))) + return 0; + while (PACKET_remaining(&inner) > 0) { + if (!TEST_true(PACKET_get_net_2(&inner, &val))) + return 0; + if (is_grease(val)) + found_grease_sigalg = 1; + } + } + } + + if (!TEST_true(found_grease_cipher)) + return 0; + if (!TEST_int_eq(found_grease_ext, 2)) + return 0; + if (!TEST_true(found_grease_version)) + return 0; + if (!TEST_true(found_grease_group)) + return 0; + if (!TEST_true(found_grease_kshare)) + return 0; + if (!TEST_true(found_grease_sigalg)) + return 0; + + return 1; +} + +static int test_grease(void) +{ + SSL_CTX *sctx = NULL, *cctx = NULL; + SSL *serverssl = NULL, *clientssl = NULL; + int testresult = 0; + + grease_ch_buf = NULL; + grease_ch_len = 0; + + if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(), + TLS_client_method(), + TLS1_3_VERSION, TLS1_3_VERSION, + &sctx, &cctx, cert, privkey))) + goto end; + + SSL_CTX_set_options(cctx, SSL_OP_GREASE); + + if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, + &clientssl, NULL, NULL))) + goto end; + + SSL_set_msg_callback(clientssl, grease_msg_cb); + + /* A full handshake should succeed - server must tolerate GREASE */ + if (!TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE))) + goto end; + + /* Now verify the captured ClientHello contains GREASE values */ + if (!TEST_true(check_grease_in_client_hello())) + goto end; + + testresult = 1; + +end: + OPENSSL_free(grease_ch_buf); + grease_ch_buf = NULL; + grease_ch_len = 0; + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} +#endif /* !defined(OSSL_NO_USABLE_TLS1_3) */ + static int test_ssl_conf_flags(void) { SSL_CONF_CTX *cctx = NULL; @@ -14655,6 +14876,9 @@ int setup_tests(void) ADD_ALL_TESTS(test_ssl_set_groups_unsupported_keyshare, 2); ADD_TEST(test_ssl_conf_flags); ADD_ALL_TESTS(test_http_verbs, 3); +#if !defined(OSSL_NO_USABLE_TLS1_3) + ADD_TEST(test_grease); +#endif return 1; err: -- 2.47.3