Skip to content

Commit 5957a9a

Browse files
kazuhoclaude
andcommitted
collapse ech offered/offered_grease/accepted flags into a state enum
The three bit fields encoded only four reachable states. Replace them with a single state enum (NONE/OFFERED/ACCEPTED/GREASE); GREASE is client-only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0208e20 commit 5957a9a

File tree

1 file changed

+34
-29
lines changed

1 file changed

+34
-29
lines changed

lib/picotls.c

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
182182
struct 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 && !ech->offered_grease) {
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 && !ech->offered_grease) {
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;
@@ -2397,7 +2404,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
23972404
/* zero-length config indicates ECH greasing */
23982405
client_setup_ech_grease(&tls->ech, tls->ctx->random_bytes, tls->ctx->ech.client.kems, tls->ctx->ech.client.ciphers,
23992406
sni_name);
2400-
tls->ech.offered_grease = 1;
2407+
tls->ech.state = PTLS_ECH_STATE_GREASE;
24012408
}
24022409
}
24032410
}
@@ -2561,10 +2568,10 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_
25612568
memcpy(tls->ech.client.first_ech.base,
25622569
emitter->buf->base + ech_size_offset - outer_ech_header_size(tls->ech.client.enc.len), len);
25632570
tls->ech.client.first_ech.len = len;
2564-
if (!tls->ech.offered_grease)
2565-
tls->ech.offered = 1;
2571+
if (tls->ech.state != PTLS_ECH_STATE_GREASE)
2572+
tls->ech.state = PTLS_ECH_STATE_OFFERED;
25662573
}
2567-
if (tls->ech.offered_grease) {
2574+
if (tls->ech.state == PTLS_ECH_STATE_GREASE) {
25682575
/* For grease ECH, the server sees the outer CH. Discard the inner state and adopt the outer, then compute the PSK
25692576
* binder over the outer CH using the standard flow. */
25702577
for (size_t i = 0; i < tls->key_schedule->num_hashes; ++i) {
@@ -2723,7 +2730,7 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c
27232730
break;
27242731
case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO:
27252732
assert(sh->is_retry_request);
2726-
if (!(tls->ech.offered || tls->ech.offered_grease)) {
2733+
if (tls->ech.state == PTLS_ECH_STATE_NONE) {
27272734
ret = PTLS_ALERT_UNSUPPORTED_EXTENSION;
27282735
goto Exit;
27292736
}
@@ -2824,9 +2831,11 @@ static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t con
28242831
if ((ret = ech_calc_confirmation(tls->key_schedule, confirm_hash_expected, tls->ech.inner_client_random, label, message)) !=
28252832
0)
28262833
goto Exit;
2827-
tls->ech.accepted = ptls_mem_equal(confirm_hash_delivered, confirm_hash_expected, sizeof(confirm_hash_delivered));
2834+
tls->ech.state = ptls_mem_equal(confirm_hash_delivered, confirm_hash_expected, sizeof(confirm_hash_delivered))
2835+
? PTLS_ECH_STATE_ACCEPTED
2836+
: PTLS_ECH_STATE_OFFERED;
28282837
memcpy(message.base + confirm_hash_off, confirm_hash_delivered, sizeof(confirm_hash_delivered));
2829-
if (tls->ech.accepted)
2838+
if (tls->ech.state == PTLS_ECH_STATE_ACCEPTED)
28302839
goto Exit;
28312840
}
28322841

@@ -2836,8 +2845,8 @@ static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t con
28362845
key_schedule_select_outer(tls->key_schedule);
28372846

28382847
Exit:
2839-
PTLS_PROBE(ECH_SELECTION, tls, !!tls->ech.accepted);
2840-
PTLS_LOG_CONN(ech_selection, tls, { PTLS_LOG_ELEMENT_BOOL(is_ech, tls->ech.accepted); });
2848+
PTLS_PROBE(ECH_SELECTION, tls, tls->ech.state == PTLS_ECH_STATE_ACCEPTED);
2849+
PTLS_LOG_CONN(ech_selection, tls, { PTLS_LOG_ELEMENT_BOOL(is_ech, tls->ech.state == PTLS_ECH_STATE_ACCEPTED); });
28412850
ptls_clear_memory(confirm_hash_expected, sizeof(confirm_hash_expected));
28422851
return ret;
28432852
}
@@ -2863,11 +2872,9 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
28632872
key_schedule_transform_post_ch1hash(tls->key_schedule);
28642873
if (tls->ech.aead != NULL) {
28652874
size_t confirm_hash_off = 0;
2866-
if (tls->ech.offered) {
2875+
if (tls->ech.state != PTLS_ECH_STATE_GREASE) {
28672876
if (sh.retry_request.ech != NULL)
28682877
confirm_hash_off = sh.retry_request.ech - message.base;
2869-
} else {
2870-
assert(tls->ech.offered_grease);
28712878
}
28722879
if ((ret = client_ech_select_hello(tls, message, confirm_hash_off, ECH_CONFIRMATION_HRR)) != 0)
28732880
goto Exit;
@@ -2883,11 +2890,9 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
28832890
/* check if ECH is accepted */
28842891
if (tls->ech.aead != NULL) {
28852892
size_t confirm_hash_off = 0;
2886-
if (tls->ech.offered) {
2893+
if (tls->ech.state != PTLS_ECH_STATE_GREASE) {
28872894
confirm_hash_off =
28882895
PTLS_HANDSHAKE_HEADER_SIZE + 2 /* legacy_version */ + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH;
2889-
} else {
2890-
assert(tls->ech.offered_grease);
28912896
}
28922897
if ((ret = client_ech_select_hello(tls, message, confirm_hash_off, ECH_CONFIRMATION_SERVER_HELLO)) != 0)
28932898
goto Exit;
@@ -3036,15 +3041,15 @@ static int client_handle_encrypted_extensions(ptls_t *tls, ptls_iovec_t message,
30363041
break;
30373042
case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO: {
30383043
/* accept retry_configs only if we offered ECH (or grease) but rejected */
3039-
if (!((tls->ech.offered || tls->ech.offered_grease) && !ptls_is_ech_handshake(tls, NULL, NULL, NULL))) {
3044+
if (!(tls->ech.state == PTLS_ECH_STATE_OFFERED || tls->ech.state == PTLS_ECH_STATE_GREASE)) {
30403045
ret = PTLS_ALERT_UNSUPPORTED_EXTENSION;
30413046
goto Exit;
30423047
}
30433048
/* parse retry_config, and if it is applicable, provide that to the application (grease clients just verify syntax) */
30443049
struct st_decoded_ech_config_t decoded;
30453050
if ((ret = client_decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0)
30463051
goto Exit;
3047-
if (tls->ech.offered_grease) {
3052+
if (tls->ech.state == PTLS_ECH_STATE_GREASE) {
30483053
/* GREASE clients ignore retry_configs after verifying the syntax */
30493054
} else if (decoded.kem != NULL && decoded.cipher != NULL && properties != NULL &&
30503055
properties->client.ech.retry_configs != NULL) {
@@ -3315,7 +3320,7 @@ static int handle_certificate(ptls_t *tls, const uint8_t *src, const uint8_t *en
33153320
if (tls->ctx->verify_certificate != NULL) {
33163321
const char *server_name = NULL;
33173322
if (!ptls_is_server(tls)) {
3318-
if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL)) {
3323+
if (tls->ech.state == PTLS_ECH_STATE_OFFERED) {
33193324
server_name = tls->ech.client.public_name;
33203325
} else {
33213326
server_name = tls->server_name;
@@ -3486,7 +3491,7 @@ static int server_handle_certificate_verify(ptls_t *tls, ptls_iovec_t message)
34863491
static int client_handle_finished(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message)
34873492
{
34883493
uint8_t send_secret[PTLS_MAX_DIGEST_SIZE];
3489-
int alert_ech_required = tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL), ret;
3494+
int alert_ech_required = tls->ech.state == PTLS_ECH_STATE_OFFERED, ret;
34903495

34913496
if ((ret = verify_finished(tls, message)) != 0)
34923497
goto Exit;
@@ -4431,7 +4436,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
44314436
goto Exit;
44324437
}
44334438
if (!is_second_flight)
4434-
tls->ech.offered = 1;
4439+
tls->ech.state = PTLS_ECH_STATE_OFFERED;
44354440
/* obtain AEAD context for opening inner CH */
44364441
if (!is_second_flight && ch->ech.payload.base != NULL && tls->ctx->ech.server.create_opener != NULL) {
44374442
if ((tls->ech.aead = tls->ctx->ech.server.create_opener->cb(
@@ -4454,7 +4459,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
44544459
memset(ech.ch_outer_aad + (ch->ech.payload.base - (message.base + PTLS_HANDSHAKE_HEADER_SIZE)), 0, ch->ech.payload.len);
44554460
if (ptls_aead_decrypt(tls->ech.aead, ech.encoded_ch_inner, ch->ech.payload.base, ch->ech.payload.len, is_second_flight,
44564461
ech.ch_outer_aad, message.len - PTLS_HANDSHAKE_HEADER_SIZE) != SIZE_MAX) {
4457-
tls->ech.accepted = 1;
4462+
tls->ech.state = PTLS_ECH_STATE_ACCEPTED;
44584463
/* successfully decrypted EncodedCHInner, build CHInner */
44594464
if ((ret = rebuild_ch_inner(&ech.ch_inner, ech.encoded_ch_inner,
44604465
ech.encoded_ch_inner + ch->ech.payload.len - tls->ech.aead->algo->tag_size, ch,
@@ -4482,7 +4487,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
44824487
tls->ech.aead = NULL;
44834488
}
44844489
}
4485-
} else if (tls->ech.offered) {
4490+
} else if (tls->ech.state != PTLS_ECH_STATE_NONE) {
44864491
assert(is_second_flight);
44874492
ret = PTLS_ALERT_ILLEGAL_PARAMETER;
44884493
goto Exit;
@@ -4856,7 +4861,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
48564861
if (tls->pending_handshake_secret != NULL)
48574862
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
48584863
/* send ECH retry_configs, if ECH was offered by rejected, even though we (the server) could have accepted ECH */
4859-
if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL) && tls->ctx->ech.server.create_opener != NULL &&
4864+
if (tls->ech.state == PTLS_ECH_STATE_OFFERED && tls->ctx->ech.server.create_opener != NULL &&
48604865
tls->ctx->ech.server.retry_configs.len != 0)
48614866
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
48624867
ptls_buffer_pushv(sendbuf, tls->ctx->ech.server.retry_configs.base, tls->ctx->ech.server.retry_configs.len);
@@ -5622,7 +5627,7 @@ int ptls_is_psk_handshake(ptls_t *tls)
56225627

56235628
int ptls_is_ech_handshake(ptls_t *tls, uint8_t *config_id, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher)
56245629
{
5625-
if (tls->ech.accepted) {
5630+
if (tls->ech.state == PTLS_ECH_STATE_ACCEPTED) {
56265631
if (config_id != NULL)
56275632
*config_id = tls->ech.config_id;
56285633
if (kem != NULL)

0 commit comments

Comments
 (0)