mirror of
https://github.com/openssl/openssl.git
synced 2026-05-07 20:12:39 +00:00
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 <tomas@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.foundation> Reviewed-by: Neil Horman <nhorman@openssl.org> MergeDate: Tue Mar 17 14:58:25 2026 (Merged from https://github.com/openssl/openssl/pull/30303)
This commit is contained in:
+20
@@ -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`
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -76,6 +76,7 @@ B<openssl> B<s_client>
|
||||
[B<-sctp>]
|
||||
[B<-sctp_label_bug>]
|
||||
[B<-fallback_scsv>]
|
||||
[B<-grease>]
|
||||
[B<-async>]
|
||||
[B<-maxfraglen> I<len>]
|
||||
[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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
+225
-1
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user