@@ -180,9 +180,16 @@ struct st_decoded_ech_config_t {
180180 * Properties for ECH. Iff ECH is used and not rejected, `aead` is non-NULL.
181181 */
182182struct st_ptls_ech_t {
183- uint8_t offered : 1 ;
184- uint8_t offered_grease : 1 ;
185- uint8_t accepted : 1 ;
183+ /**
184+ * ECH state for this connection. `OFFERED` and `ACCEPTED` are used on both client and server; `GREASE` is client-only (server
185+ * cannot distinguish GREASE from a config mismatch, both are simply ECH that fails to decrypt).
186+ */
187+ enum en_ptls_ech_state_t {
188+ PTLS_ECH_STATE_NONE = 0 ,
189+ PTLS_ECH_STATE_OFFERED ,
190+ PTLS_ECH_STATE_ACCEPTED ,
191+ PTLS_ECH_STATE_GREASE
192+ } state ;
186193 uint8_t config_id ;
187194 ptls_hpke_kem_t * kem ;
188195 ptls_hpke_cipher_suite_t * cipher ;
@@ -2305,7 +2312,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum
23052312 buffer_push_extension (sendbuf , PTLS_EXTENSION_TYPE_PRE_SHARED_KEY , {
23062313 ptls_buffer_push_block (sendbuf , 2 , {
23072314 ptls_buffer_push_block (sendbuf , 2 , {
2308- if (mode == ENCODE_CH_MODE_OUTER ) {
2315+ if (mode == ENCODE_CH_MODE_OUTER && ech -> state != PTLS_ECH_STATE_GREASE ) {
23092316 if ((ret = ptls_buffer_reserve (sendbuf , psk_identity .len )) != 0 )
23102317 goto Exit ;
23112318 ctx -> random_bytes (sendbuf -> base + sendbuf -> off , psk_identity .len );
@@ -2315,7 +2322,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum
23152322 }
23162323 });
23172324 uint32_t age ;
2318- if (mode == ENCODE_CH_MODE_OUTER ) {
2325+ if (mode == ENCODE_CH_MODE_OUTER && ech -> state != PTLS_ECH_STATE_GREASE ) {
23192326 ctx -> random_bytes (& age , sizeof (age ));
23202327 } else {
23212328 age = obfuscated_ticket_age ;
@@ -2341,6 +2348,28 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum
23412348 return ret ;
23422349}
23432350
2351+ /**
2352+ * Feeds the CH message into the hash, computing the PSK binder if necessary. `binder_key` must be derived before calling this
2353+ * function.
2354+ */
2355+ static int update_ch_hash_and_binder (ptls_key_schedule_t * ks , uint8_t * ch , size_t ch_start , size_t ch_end , int has_psk ,
2356+ uint8_t * binder_key , int is_outer )
2357+ {
2358+ int ret = 0 ;
2359+ size_t hash_off = ch_start ;
2360+
2361+ if (has_psk ) {
2362+ size_t psk_binder_off = ch_end - (3 + ks -> hashes [0 ].algo -> digest_size );
2363+ ptls__key_schedule_update_hash (ks , ch + hash_off , psk_binder_off - hash_off , is_outer );
2364+ hash_off = psk_binder_off ;
2365+ if ((ret = calc_verify_data (ch + psk_binder_off + 3 , ks , binder_key )) != 0 )
2366+ return ret ;
2367+ }
2368+ ptls__key_schedule_update_hash (ks , ch + hash_off , ch_end - hash_off , is_outer );
2369+
2370+ return ret ;
2371+ }
2372+
23442373static int send_client_hello (ptls_t * tls , ptls_message_emitter_t * emitter , ptls_handshake_properties_t * properties ,
23452374 ptls_iovec_t * cookie )
23462375{
@@ -2351,7 +2380,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
23512380 } psk = {{NULL }};
23522381 uint32_t obfuscated_ticket_age = 0 ;
23532382 const char * sni_name = NULL ;
2354- size_t mess_start , msghash_off ;
2383+ size_t mess_start ;
23552384 uint8_t binder_key [PTLS_MAX_DIGEST_SIZE ];
23562385 ptls_buffer_t encoded_ch_inner ;
23572386 int ret , is_second_flight = tls -> key_schedule != NULL ;
@@ -2371,10 +2400,11 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
23712400 if ((ret = client_setup_ech (& tls -> ech , & decoded , tls -> ctx -> random_bytes )) != 0 )
23722401 goto Exit ;
23732402 }
2374- } else {
2375- /* zero-length config indicates ECH greasing */
2403+ } else if ( properties -> client . ech . configs . base != NULL ) {
2404+ /* zero-length config with non-NULL base indicates ECH greasing; NULL base means no ECH */
23762405 client_setup_ech_grease (& tls -> ech , tls -> ctx -> random_bytes , tls -> ctx -> ech .client .kems , tls -> ctx -> ech .client .ciphers ,
23772406 sni_name );
2407+ tls -> ech .state = PTLS_ECH_STATE_GREASE ;
23782408 }
23792409 }
23802410 }
@@ -2462,7 +2492,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
24622492 /* start generating CH */
24632493 if ((ret = emitter -> begin_message (emitter )) != 0 )
24642494 goto Exit ;
2465- mess_start = msghash_off = emitter -> buf -> off ;
2495+ mess_start = emitter -> buf -> off ;
24662496
24672497 /* generate true (inner) CH */
24682498 if ((ret = encode_client_hello (tls -> ctx , emitter -> buf , ENCODE_CH_MODE_INNER , is_second_flight , properties ,
@@ -2474,15 +2504,12 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
24742504
24752505 /* update the message hash, filling in the PSK binder HMAC if necessary */
24762506 if (psk .secret .base != NULL ) {
2477- size_t psk_binder_off = emitter -> buf -> off - (3 + tls -> key_schedule -> hashes [0 ].algo -> digest_size );
24782507 if ((ret = derive_secret_with_empty_digest (tls -> key_schedule , binder_key , psk .label )) != 0 )
24792508 goto Exit ;
2480- ptls__key_schedule_update_hash (tls -> key_schedule , emitter -> buf -> base + msghash_off , psk_binder_off - msghash_off , 0 );
2481- msghash_off = psk_binder_off ;
2482- if ((ret = calc_verify_data (emitter -> buf -> base + psk_binder_off + 3 , tls -> key_schedule , binder_key )) != 0 )
2483- goto Exit ;
24842509 }
2485- ptls__key_schedule_update_hash (tls -> key_schedule , emitter -> buf -> base + msghash_off , emitter -> buf -> off - msghash_off , 0 );
2510+ if ((ret = update_ch_hash_and_binder (tls -> key_schedule , emitter -> buf -> base , mess_start , emitter -> buf -> off ,
2511+ psk .secret .base != NULL , binder_key , 0 )) != 0 )
2512+ goto Exit ;
24862513
24872514 /* ECH */
24882515 if (tls -> ech .aead != NULL ) {
@@ -2541,14 +2568,26 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
25412568 memcpy (tls -> ech .client .first_ech .base ,
25422569 emitter -> buf -> base + ech_size_offset - outer_ech_header_size (tls -> ech .client .enc .len ), len );
25432570 tls -> ech .client .first_ech .len = len ;
2544- if (properties -> client .ech .configs .len != 0 ) {
2545- tls -> ech .offered = 1 ;
2546- } else {
2547- tls -> ech .offered_grease = 1 ;
2571+ if (tls -> ech .state != PTLS_ECH_STATE_GREASE )
2572+ tls -> ech .state = PTLS_ECH_STATE_OFFERED ;
2573+ }
2574+ if (tls -> ech .state == PTLS_ECH_STATE_GREASE ) {
2575+ /* For grease ECH, the server sees the outer CH. Discard the inner state and adopt the outer, then compute the PSK
2576+ * binder over the outer CH using the standard flow. */
2577+ for (size_t i = 0 ; i < tls -> key_schedule -> num_hashes ; ++ i ) {
2578+ tls -> key_schedule -> hashes [i ].ctx -> final (tls -> key_schedule -> hashes [i ].ctx , NULL , PTLS_HASH_FINAL_MODE_FREE );
2579+ tls -> key_schedule -> hashes [i ].ctx = tls -> key_schedule -> hashes [i ].ctx_outer ;
2580+ tls -> key_schedule -> hashes [i ].ctx_outer = NULL ;
25482581 }
2582+ ptls_aead_free (tls -> ech .aead );
2583+ tls -> ech .aead = NULL ;
2584+ if ((ret = update_ch_hash_and_binder (tls -> key_schedule , emitter -> buf -> base , mess_start , emitter -> buf -> off ,
2585+ psk .secret .base != NULL , binder_key , 0 )) != 0 )
2586+ goto Exit ;
2587+ } else {
2588+ /* update outer hash */
2589+ ptls__key_schedule_update_hash (tls -> key_schedule , emitter -> buf -> base + mess_start , emitter -> buf -> off - mess_start , 1 );
25492590 }
2550- /* update hash */
2551- ptls__key_schedule_update_hash (tls -> key_schedule , emitter -> buf -> base + mess_start , emitter -> buf -> off - mess_start , 1 );
25522591 }
25532592
25542593 /* commit CH to the record layer */
@@ -2691,7 +2730,7 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c
26912730 break ;
26922731 case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO :
26932732 assert (sh -> is_retry_request );
2694- if (!( tls -> ech .offered || tls -> ech . offered_grease ) ) {
2733+ if (tls -> ech .state == PTLS_ECH_STATE_NONE ) {
26952734 ret = PTLS_ALERT_UNSUPPORTED_EXTENSION ;
26962735 goto Exit ;
26972736 }
@@ -2792,10 +2831,16 @@ static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t con
27922831 if ((ret = ech_calc_confirmation (tls -> key_schedule , confirm_hash_expected , tls -> ech .inner_client_random , label , message )) !=
27932832 0 )
27942833 goto Exit ;
2795- tls -> ech . accepted = ptls_mem_equal (confirm_hash_delivered , confirm_hash_expected , sizeof (confirm_hash_delivered ));
2834+ int accepted = ptls_mem_equal (confirm_hash_delivered , confirm_hash_expected , sizeof (confirm_hash_delivered ));
27962835 memcpy (message .base + confirm_hash_off , confirm_hash_delivered , sizeof (confirm_hash_delivered ));
2797- if (tls -> ech .accepted )
2836+ if (accepted ) {
2837+ tls -> ech .state = PTLS_ECH_STATE_ACCEPTED ;
2838+ goto Exit ;
2839+ } else if (tls -> ech .state == PTLS_ECH_STATE_ACCEPTED ) {
2840+ /* Per RFC 9849 Section 6.1.5: if HRR confirmed ECH acceptance, ServerHello MUST also confirm it. */
2841+ ret = PTLS_ALERT_ILLEGAL_PARAMETER ;
27982842 goto Exit ;
2843+ }
27992844 }
28002845
28012846 /* dispose ECH AEAD state to indicate rejection, adopting outer CH for the rest of the handshake */
@@ -2804,8 +2849,8 @@ static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t con
28042849 key_schedule_select_outer (tls -> key_schedule );
28052850
28062851Exit :
2807- PTLS_PROBE (ECH_SELECTION , tls , !! tls -> ech .accepted );
2808- PTLS_LOG_CONN (ech_selection , tls , { PTLS_LOG_ELEMENT_BOOL (is_ech , tls -> ech .accepted ); });
2852+ PTLS_PROBE (ECH_SELECTION , tls , tls -> ech .state == PTLS_ECH_STATE_ACCEPTED );
2853+ PTLS_LOG_CONN (ech_selection , tls , { PTLS_LOG_ELEMENT_BOOL (is_ech , tls -> ech .state == PTLS_ECH_STATE_ACCEPTED ); });
28092854 ptls_clear_memory (confirm_hash_expected , sizeof (confirm_hash_expected ));
28102855 return ret ;
28112856}
@@ -2831,11 +2876,9 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
28312876 key_schedule_transform_post_ch1hash (tls -> key_schedule );
28322877 if (tls -> ech .aead != NULL ) {
28332878 size_t confirm_hash_off = 0 ;
2834- if (tls -> ech .offered ) {
2879+ if (tls -> ech .state != PTLS_ECH_STATE_GREASE ) {
28352880 if (sh .retry_request .ech != NULL )
28362881 confirm_hash_off = sh .retry_request .ech - message .base ;
2837- } else {
2838- assert (tls -> ech .offered_grease );
28392882 }
28402883 if ((ret = client_ech_select_hello (tls , message , confirm_hash_off , ECH_CONFIRMATION_HRR )) != 0 )
28412884 goto Exit ;
@@ -2851,11 +2894,9 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
28512894 /* check if ECH is accepted */
28522895 if (tls -> ech .aead != NULL ) {
28532896 size_t confirm_hash_off = 0 ;
2854- if (tls -> ech .offered ) {
2897+ if (tls -> ech .state != PTLS_ECH_STATE_GREASE ) {
28552898 confirm_hash_off =
28562899 PTLS_HANDSHAKE_HEADER_SIZE + 2 /* legacy_version */ + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH ;
2857- } else {
2858- assert (tls -> ech .offered_grease );
28592900 }
28602901 if ((ret = client_ech_select_hello (tls , message , confirm_hash_off , ECH_CONFIRMATION_SERVER_HELLO )) != 0 )
28612902 goto Exit ;
@@ -3003,17 +3044,19 @@ static int client_handle_encrypted_extensions(ptls_t *tls, ptls_iovec_t message,
30033044 src = end ;
30043045 break ;
30053046 case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO : {
3006- /* accept retry_configs only if we offered ECH but rejected */
3007- if (!(( tls -> ech .offered || tls -> ech .offered_grease ) && ! ptls_is_ech_handshake ( tls , NULL , NULL , NULL ) )) {
3047+ /* accept retry_configs only if we offered ECH (or grease) but rejected */
3048+ if (!(tls -> ech .state == PTLS_ECH_STATE_OFFERED || tls -> ech .state == PTLS_ECH_STATE_GREASE )) {
30083049 ret = PTLS_ALERT_UNSUPPORTED_EXTENSION ;
30093050 goto Exit ;
30103051 }
3011- /* parse retry_config, and if it is applicable, provide that to the application */
3052+ /* parse retry_config, and if it is applicable, provide that to the application (grease clients just verify syntax) */
30123053 struct st_decoded_ech_config_t decoded ;
30133054 if ((ret = client_decode_ech_config_list (tls -> ctx , & decoded , ptls_iovec_init (src , end - src ))) != 0 )
30143055 goto Exit ;
3015- if (decoded .kem != NULL && decoded .cipher != NULL && properties != NULL &&
3016- properties -> client .ech .retry_configs != NULL ) {
3056+ if (tls -> ech .state == PTLS_ECH_STATE_GREASE ) {
3057+ /* GREASE clients ignore retry_configs after verifying the syntax */
3058+ } else if (decoded .kem != NULL && decoded .cipher != NULL && properties != NULL &&
3059+ properties -> client .ech .retry_configs != NULL ) {
30173060 if ((properties -> client .ech .retry_configs -> base = malloc (end - src )) == NULL ) {
30183061 ret = PTLS_ERROR_NO_MEMORY ;
30193062 goto Exit ;
@@ -3281,7 +3324,7 @@ static int handle_certificate(ptls_t *tls, const uint8_t *src, const uint8_t *en
32813324 if (tls -> ctx -> verify_certificate != NULL ) {
32823325 const char * server_name = NULL ;
32833326 if (!ptls_is_server (tls )) {
3284- if (tls -> ech .offered && ! ptls_is_ech_handshake ( tls , NULL , NULL , NULL ) ) {
3327+ if (tls -> ech .state == PTLS_ECH_STATE_OFFERED ) {
32853328 server_name = tls -> ech .client .public_name ;
32863329 } else {
32873330 server_name = tls -> server_name ;
@@ -3452,7 +3495,7 @@ static int server_handle_certificate_verify(ptls_t *tls, ptls_iovec_t message)
34523495static int client_handle_finished (ptls_t * tls , ptls_message_emitter_t * emitter , ptls_iovec_t message )
34533496{
34543497 uint8_t send_secret [PTLS_MAX_DIGEST_SIZE ];
3455- int alert_ech_required = tls -> ech .offered && ! ptls_is_ech_handshake ( tls , NULL , NULL , NULL ) , ret ;
3498+ int alert_ech_required = tls -> ech .state == PTLS_ECH_STATE_OFFERED , ret ;
34563499
34573500 if ((ret = verify_finished (tls , message )) != 0 )
34583501 goto Exit ;
@@ -4397,7 +4440,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
43974440 goto Exit ;
43984441 }
43994442 if (!is_second_flight )
4400- tls -> ech .offered = 1 ;
4443+ tls -> ech .state = PTLS_ECH_STATE_OFFERED ;
44014444 /* obtain AEAD context for opening inner CH */
44024445 if (!is_second_flight && ch -> ech .payload .base != NULL && tls -> ctx -> ech .server .create_opener != NULL ) {
44034446 if ((tls -> ech .aead = tls -> ctx -> ech .server .create_opener -> cb (
@@ -4420,7 +4463,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
44204463 memset (ech .ch_outer_aad + (ch -> ech .payload .base - (message .base + PTLS_HANDSHAKE_HEADER_SIZE )), 0 , ch -> ech .payload .len );
44214464 if (ptls_aead_decrypt (tls -> ech .aead , ech .encoded_ch_inner , ch -> ech .payload .base , ch -> ech .payload .len , is_second_flight ,
44224465 ech .ch_outer_aad , message .len - PTLS_HANDSHAKE_HEADER_SIZE ) != SIZE_MAX ) {
4423- tls -> ech .accepted = 1 ;
4466+ tls -> ech .state = PTLS_ECH_STATE_ACCEPTED ;
44244467 /* successfully decrypted EncodedCHInner, build CHInner */
44254468 if ((ret = rebuild_ch_inner (& ech .ch_inner , ech .encoded_ch_inner ,
44264469 ech .encoded_ch_inner + ch -> ech .payload .len - tls -> ech .aead -> algo -> tag_size , ch ,
@@ -4448,7 +4491,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
44484491 tls -> ech .aead = NULL ;
44494492 }
44504493 }
4451- } else if (tls -> ech .offered ) {
4494+ } else if (tls -> ech .state != PTLS_ECH_STATE_NONE ) {
44524495 assert (is_second_flight );
44534496 ret = PTLS_ALERT_ILLEGAL_PARAMETER ;
44544497 goto Exit ;
@@ -4822,7 +4865,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
48224865 if (tls -> pending_handshake_secret != NULL )
48234866 buffer_push_extension (sendbuf , PTLS_EXTENSION_TYPE_EARLY_DATA , {});
48244867 /* send ECH retry_configs, if ECH was offered by rejected, even though we (the server) could have accepted ECH */
4825- if (tls -> ech .offered && ! ptls_is_ech_handshake ( tls , NULL , NULL , NULL ) && tls -> ctx -> ech .server .create_opener != NULL &&
4868+ if (tls -> ech .state == PTLS_ECH_STATE_OFFERED && tls -> ctx -> ech .server .create_opener != NULL &&
48264869 tls -> ctx -> ech .server .retry_configs .len != 0 )
48274870 buffer_push_extension (sendbuf , PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO , {
48284871 ptls_buffer_pushv (sendbuf , tls -> ctx -> ech .server .retry_configs .base , tls -> ctx -> ech .server .retry_configs .len );
@@ -5588,7 +5631,7 @@ int ptls_is_psk_handshake(ptls_t *tls)
55885631
55895632int ptls_is_ech_handshake (ptls_t * tls , uint8_t * config_id , ptls_hpke_kem_t * * kem , ptls_hpke_cipher_suite_t * * cipher )
55905633{
5591- if (tls -> ech .accepted ) {
5634+ if (tls -> ech .state == PTLS_ECH_STATE_ACCEPTED ) {
55925635 if (config_id != NULL )
55935636 * config_id = tls -> ech .config_id ;
55945637 if (kem != NULL )
0 commit comments