/* * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifdef _WINDOWS #include "winsock.h" #endif #include #include #include #include #include #ifndef _WINDOWS #include #include #include #include #include #endif #include "picotls.h" #if PICOTLS_USE_DTRACE #include "picotls-probes.h" #endif #define PTLS_MAX_PLAINTEXT_RECORD_SIZE 16384 #define PTLS_MAX_ENCRYPTED_RECORD_SIZE (16384 + 256) #define PTLS_RECORD_VERSION_MAJOR 3 #define PTLS_RECORD_VERSION_MINOR 3 #define PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC 20 #define PTLS_CONTENT_TYPE_ALERT 21 #define PTLS_CONTENT_TYPE_HANDSHAKE 22 #define PTLS_CONTENT_TYPE_APPDATA 23 #define PTLS_PSK_KE_MODE_PSK 0 #define PTLS_PSK_KE_MODE_PSK_DHE 1 #define PTLS_HANDSHAKE_HEADER_SIZE 4 #define PTLS_EXTENSION_TYPE_SERVER_NAME 0 #define PTLS_EXTENSION_TYPE_STATUS_REQUEST 5 #define PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS 10 #define PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS 13 #define PTLS_EXTENSION_TYPE_ALPN 16 #define PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE 20 #define PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE 27 #define PTLS_EXTENSION_TYPE_PRE_SHARED_KEY 41 #define PTLS_EXTENSION_TYPE_EARLY_DATA 42 #define PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS 43 #define PTLS_EXTENSION_TYPE_COOKIE 44 #define PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES 45 #define PTLS_EXTENSION_TYPE_KEY_SHARE 51 #define PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS 0xfd00 #define PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO 0xfe0d #define PTLS_SERVER_NAME_TYPE_HOSTNAME 0 #define PTLS_ECH_CONFIG_VERSION 0xfe0d #define PTLS_ECH_CLIENT_HELLO_TYPE_OUTER 0 #define PTLS_ECH_CLIENT_HELLO_TYPE_INNER 1 #define PTLS_ECH_CONFIRM_LENGTH 8 static const char ech_info_prefix[8] = "tls ech"; #define PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, server CertificateVerify" #define PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, client CertificateVerify" #define PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE \ (64 + sizeof(PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING) + PTLS_MAX_DIGEST_SIZE * 2) #define PTLS_EARLY_DATA_MAX_DELAY 10000 /* max. RTT (in msec) to permit early data */ #ifndef PTLS_MAX_EARLY_DATA_SKIP_SIZE #define PTLS_MAX_EARLY_DATA_SKIP_SIZE 65536 #endif #if defined(PTLS_DEBUG) && PTLS_DEBUG #define PTLS_DEBUGF(...) fprintf(stderr, __VA_ARGS__) #else #define PTLS_DEBUGF(...) #endif #ifndef PTLS_MEMORY_DEBUG #define PTLS_MEMORY_DEBUG 0 #endif #if PICOTLS_USE_DTRACE #define PTLS_SHOULD_PROBE(LABEL, tls) (PTLS_UNLIKELY(PICOTLS_##LABEL##_ENABLED()) && !(tls)->skip_tracing) #define PTLS_PROBE0(LABEL, tls) \ do { \ ptls_t *_tls = (tls); \ if (PTLS_SHOULD_PROBE(LABEL, _tls)) \ PICOTLS_##LABEL(_tls); \ } while (0) #define PTLS_PROBE(LABEL, tls, ...) \ do { \ ptls_t *_tls = (tls); \ if (PTLS_SHOULD_PROBE(LABEL, _tls)) \ PICOTLS_##LABEL(_tls, __VA_ARGS__); \ } while (0) #else #define PTLS_PROBE0(LABEL, tls) #define PTLS_PROBE(LABEL, tls, ...) #endif /** * list of supported versions in the preferred order */ static const uint16_t supported_versions[] = {PTLS_PROTOCOL_VERSION_TLS13}; static const uint8_t hello_retry_random[PTLS_HELLO_RANDOM_SIZE] = {0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C}; struct st_ptls_traffic_protection_t { uint8_t secret[PTLS_MAX_DIGEST_SIZE]; size_t epoch; /* the following fields are not used if the key_change callback is set */ ptls_aead_context_t *aead; uint64_t seq; unsigned tls12 : 1; uint64_t tls12_enc_record_iv; }; struct st_ptls_record_message_emitter_t { ptls_message_emitter_t super; size_t rec_start; }; struct st_ptls_signature_algorithms_t { uint16_t list[16]; /* expand? */ size_t count; }; struct st_ptls_certificate_request_t { /** * context.base becomes non-NULL when a CertificateRequest is pending for processing */ ptls_iovec_t context; struct st_ptls_signature_algorithms_t signature_algorithms; }; struct st_decoded_ech_config_t { uint8_t id; ptls_hpke_kem_t *kem; ptls_iovec_t public_key; ptls_hpke_cipher_suite_t *cipher; uint8_t max_name_length; ptls_iovec_t public_name; ptls_iovec_t bytes; }; /** * Properties for ECH. Iff ECH is used and not rejected, `aead` is non-NULL. */ struct st_ptls_ech_t { uint8_t offered : 1; uint8_t offered_grease : 1; uint8_t accepted : 1; uint8_t config_id; ptls_hpke_kem_t *kem; ptls_hpke_cipher_suite_t *cipher; ptls_aead_context_t *aead; uint8_t inner_client_random[PTLS_HELLO_RANDOM_SIZE]; struct { ptls_iovec_t enc; uint8_t max_name_length; char *public_name; /** * retains a copy of entire ECH extension so that it can be replayed in the 2nd CH when ECH is rejected via HRR */ ptls_iovec_t first_ech; } client; }; struct st_ptls_t { /** * the context */ ptls_context_t *ctx; /** * the state */ enum en_ptls_state_t { PTLS_STATE_CLIENT_HANDSHAKE_START, PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO, PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO, PTLS_STATE_CLIENT_EXPECT_ENCRYPTED_EXTENSIONS, PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE, PTLS_STATE_CLIENT_EXPECT_CERTIFICATE, PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY, PTLS_STATE_CLIENT_EXPECT_FINISHED, PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO, PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO, PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY, PTLS_STATE_SERVER_EXPECT_CERTIFICATE, PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY, /* ptls_send can be called if the state is below here */ PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA, PTLS_STATE_SERVER_EXPECT_FINISHED, PTLS_STATE_POST_HANDSHAKE_MIN, PTLS_STATE_CLIENT_POST_HANDSHAKE = PTLS_STATE_POST_HANDSHAKE_MIN, PTLS_STATE_SERVER_POST_HANDSHAKE } state; /** * receive buffers */ struct { ptls_buffer_t rec; ptls_buffer_t mess; } recvbuf; /** * key schedule */ ptls_key_schedule_t *key_schedule; /** * values used for record protection */ struct { struct st_ptls_traffic_protection_t dec; struct st_ptls_traffic_protection_t enc; } traffic_protection; /** * server-name passed using SNI */ char *server_name; /** * result of ALPN */ char *negotiated_protocol; /** * selected key-exchange */ ptls_key_exchange_algorithm_t *key_share; /** * selected cipher-suite */ ptls_cipher_suite_t *cipher_suite; /** * ClientHello.random that appears on the wire. When ECH is used, that of inner CH is retained separately. */ uint8_t client_random[PTLS_HELLO_RANDOM_SIZE]; /** * exporter master secret (either 0rtt or 1rtt) */ struct { uint8_t *early; uint8_t *one_rtt; } exporter_master_secret; /** * ECH */ struct st_ptls_ech_t ech; /* flags */ unsigned is_server : 1; unsigned is_psk_handshake : 1; unsigned send_change_cipher_spec : 1; unsigned needs_key_update : 1; unsigned key_update_send_request : 1; unsigned skip_tracing : 1; /** * misc. */ union { struct { ptls_iovec_t legacy_session_id; uint8_t legacy_session_id_buf[32]; ptls_key_exchange_context_t *key_share_ctx; unsigned offered_psk : 1; /** * if 1-RTT write key is active */ unsigned using_early_data : 1; struct st_ptls_certificate_request_t certificate_request; } client; struct { uint8_t pending_traffic_secret[PTLS_MAX_DIGEST_SIZE]; uint32_t early_data_skipped_bytes; /* if not UINT32_MAX, the server is skipping early data */ unsigned can_send_session_ticket : 1; ptls_async_job_t *async_job; } server; }; /** * certificate verify; will be used by the client and the server (if require_client_authentication is set) */ struct { int (*cb)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature); void *verify_ctx; } certificate_verify; /** * handshake traffic secret to be commisioned (an array of `uint8_t [PTLS_MAX_DIGEST_SIZE]` or NULL) */ uint8_t *pending_handshake_secret; /** * user data */ void *data_ptr; }; struct st_ptls_record_t { uint8_t type; uint16_t version; size_t length; const uint8_t *fragment; }; struct st_ptls_client_hello_psk_t { ptls_iovec_t identity; uint32_t obfuscated_ticket_age; ptls_iovec_t binder; }; #define MAX_UNKNOWN_EXTENSIONS 16 #define MAX_CERTIFICATE_TYPES 8 struct st_ptls_client_hello_t { uint16_t legacy_version; const uint8_t *random_bytes; ptls_iovec_t legacy_session_id; struct { const uint8_t *ids; size_t count; } compression_methods; uint16_t selected_version; ptls_iovec_t cipher_suites; ptls_iovec_t negotiated_groups; ptls_iovec_t key_shares; struct st_ptls_signature_algorithms_t signature_algorithms; ptls_iovec_t server_name; struct { ptls_iovec_t list[16]; size_t count; } alpn; struct { uint16_t list[16]; size_t count; } cert_compression_algos; struct { ptls_iovec_t all; ptls_iovec_t tbs; ptls_iovec_t ch1_hash; ptls_iovec_t signature; unsigned sent_key_share : 1; } cookie; struct { uint8_t list[MAX_CERTIFICATE_TYPES]; size_t count; } server_certificate_types; unsigned status_request : 1; /** * ECH: payload.base != NULL indicates that the extension was received */ struct { uint8_t type; uint8_t config_id; ptls_hpke_cipher_suite_id_t cipher_suite; ptls_iovec_t enc; ptls_iovec_t payload; } ech; struct { const uint8_t *hash_end; struct { struct st_ptls_client_hello_psk_t list[4]; size_t count; } identities; unsigned ke_modes; unsigned early_data_indication : 1; unsigned is_last_extension : 1; } psk; ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1]; size_t first_extension_at; }; struct st_ptls_server_hello_t { uint8_t random_[PTLS_HELLO_RANDOM_SIZE]; ptls_iovec_t legacy_session_id; int is_retry_request; union { ptls_iovec_t peerkey; struct { uint16_t selected_group; ptls_iovec_t cookie; const uint8_t *ech; } retry_request; }; }; struct st_ptls_key_schedule_t { unsigned generation; /* early secret (1), hanshake secret (2), master secret (3) */ uint8_t secret[PTLS_MAX_DIGEST_SIZE]; size_t num_hashes; struct { ptls_hash_algorithm_t *algo; ptls_hash_context_t *ctx, *ctx_outer; } hashes[1]; }; struct st_ptls_extension_decoder_t { uint16_t type; int (*cb)(ptls_t *tls, void *arg, const uint8_t *src, const uint8_t *const end); }; struct st_ptls_extension_bitmap_t { uint64_t bits; }; static const uint8_t zeroes_of_max_digest_size[PTLS_MAX_DIGEST_SIZE] = {0}; static ptls_aead_context_t *new_aead(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret, ptls_iovec_t hash_value, const char *label_prefix); static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify, struct st_ptls_signature_algorithms_t *signature_algorithms); static int is_supported_version(uint16_t v) { size_t i; for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i) if (supported_versions[i] == v) return 1; return 0; } static int extension_bitmap_testandset(struct st_ptls_extension_bitmap_t *bitmap, int hstype, uint16_t extid) { #define HSTYPE_TO_BIT(hstype) ((uint64_t)1 << ((hstype) + 1)) /* min(hstype) is -1 (PSEUDO_HRR) */ #define DEFINE_BIT(abbrev, hstype) static const uint64_t abbrev = HSTYPE_TO_BIT(PTLS_HANDSHAKE_TYPE_##hstype) #define EXT(candext, allowed_bits) \ do { \ if (PTLS_UNLIKELY(extid == PTLS_EXTENSION_TYPE_##candext)) { \ allowed_hs_bits = allowed_bits; \ goto Found; \ } \ ext_bitmap_mask <<= 1; \ } while (0) DEFINE_BIT(CH, CLIENT_HELLO); DEFINE_BIT(SH, SERVER_HELLO); DEFINE_BIT(HRR, PSEUDO_HRR); DEFINE_BIT(EE, ENCRYPTED_EXTENSIONS); DEFINE_BIT(CR, CERTIFICATE_REQUEST); DEFINE_BIT(CT, CERTIFICATE); DEFINE_BIT(NST, NEW_SESSION_TICKET); uint64_t allowed_hs_bits, ext_bitmap_mask = 1; /* clang-format off */ /* RFC 8446 section 4.2: "The table below indicates the messages where a given extension may appear... If an implementation * receives an extension which it recognizes and which is not specified for the message in which it appears, it MUST abort the * handshake with an "illegal_parameter" alert. * * +-------------------------+---------------+ * + Extension | Allowed | * +-------------------------+---------------+ */ EXT( SERVER_NAME , CH + EE ); EXT( STATUS_REQUEST , CH + CR + CT ); EXT( SUPPORTED_GROUPS , CH + EE ); EXT( SIGNATURE_ALGORITHMS , CH + CR ); EXT( ALPN , CH + EE ); EXT( SERVER_CERTIFICATE_TYPE , CH + EE ); EXT( KEY_SHARE , CH + SH + HRR ); EXT( PRE_SHARED_KEY , CH + SH ); EXT( PSK_KEY_EXCHANGE_MODES , CH ); EXT( EARLY_DATA , CH + EE + NST ); EXT( COOKIE , CH + HRR ); EXT( SUPPORTED_VERSIONS , CH + SH + HRR ); EXT( COMPRESS_CERTIFICATE , CH + CR ); /* from RFC 8879 */ EXT( ENCRYPTED_CLIENT_HELLO , CH + HRR + EE ); /* from draft-ietf-tls-esni-15 */ EXT( ECH_OUTER_EXTENSIONS , 0 ); /* +-----------------------------------------+ */ /* clang-format on */ return 1; Found: if ((allowed_hs_bits & HSTYPE_TO_BIT(hstype)) == 0) return 0; if ((bitmap->bits & ext_bitmap_mask) != 0) return 0; bitmap->bits |= ext_bitmap_mask; return 1; #undef HSTYPE_TO_BIT #undef DEFINE_ABBREV #undef EXT } #ifndef ntoh16 static uint16_t ntoh16(const uint8_t *src) { return (uint16_t)src[0] << 8 | src[1]; } #endif #ifndef ntoh24 static uint32_t ntoh24(const uint8_t *src) { return (uint32_t)src[0] << 16 | (uint32_t)src[1] << 8 | src[2]; } #endif #ifndef ntoh32 static uint32_t ntoh32(const uint8_t *src) { return (uint32_t)src[0] << 24 | (uint32_t)src[1] << 16 | (uint32_t)src[2] << 8 | src[3]; } #endif #ifndef ntoh64 static uint64_t ntoh64(const uint8_t *src) { return (uint64_t)src[0] << 56 | (uint64_t)src[1] << 48 | (uint64_t)src[2] << 40 | (uint64_t)src[3] << 32 | (uint64_t)src[4] << 24 | (uint64_t)src[5] << 16 | (uint64_t)src[6] << 8 | src[7]; } #endif static void encode64(uint8_t *dst, uint64_t v) { for (size_t i = 0; i < 8; ++i) dst[i] = (uint8_t)(v >> (56 - 8 * i)); } static char *duplicate_as_str(const void *src, size_t len) { char *dst; if ((dst = malloc(len + 1)) == NULL) return NULL; memcpy(dst, src, len); dst[len] = '\0'; return dst; } void ptls_buffer__release_memory(ptls_buffer_t *buf) { ptls_clear_memory(buf->base, buf->off); if (buf->is_allocated) { #ifdef _WINDOWS if (buf->align_bits != 0) { _aligned_free(buf->base); } else { free(buf->base); } #else free(buf->base); #endif } } int ptls_buffer_reserve(ptls_buffer_t *buf, size_t delta) { return ptls_buffer_reserve_aligned(buf, delta, 0); } int ptls_buffer_reserve_aligned(ptls_buffer_t *buf, size_t delta, uint8_t align_bits) { if (buf->base == NULL) return PTLS_ERROR_NO_MEMORY; if (PTLS_MEMORY_DEBUG || buf->capacity < buf->off + delta || (buf->align_bits < align_bits && ((uintptr_t)buf->base & (((uintptr_t)1 << align_bits) - 1)) != 0)) { void *newp; size_t new_capacity = buf->capacity; if (new_capacity < 1024) new_capacity = 1024; while (new_capacity < buf->off + delta) { new_capacity *= 2; } if (align_bits != 0) { #ifdef _WINDOWS if ((newp = _aligned_malloc(new_capacity, (size_t)1 << align_bits)) == NULL) return PTLS_ERROR_NO_MEMORY; #else if (posix_memalign(&newp, 1 << align_bits, new_capacity) != 0) return PTLS_ERROR_NO_MEMORY; #endif } else { if ((newp = malloc(new_capacity)) == NULL) return PTLS_ERROR_NO_MEMORY; } memcpy(newp, buf->base, buf->off); ptls_buffer__release_memory(buf); buf->base = newp; buf->capacity = new_capacity; buf->is_allocated = 1; buf->align_bits = align_bits; } return 0; } int ptls_buffer__do_pushv(ptls_buffer_t *buf, const void *src, size_t len) { int ret; if (len == 0) return 0; if ((ret = ptls_buffer_reserve(buf, len)) != 0) return ret; memcpy(buf->base + buf->off, src, len); buf->off += len; return 0; } int ptls_buffer__adjust_quic_blocksize(ptls_buffer_t *buf, size_t body_size) { uint8_t sizebuf[PTLS_ENCODE_QUICINT_CAPACITY]; size_t sizelen = ptls_encode_quicint(sizebuf, body_size) - sizebuf; /* adjust amount of space before body_size to `sizelen` bytes */ if (sizelen != 1) { int ret; if ((ret = ptls_buffer_reserve(buf, sizelen - 1)) != 0) return ret; memmove(buf->base + buf->off - body_size - 1 + sizelen, buf->base + buf->off - body_size, body_size); buf->off += sizelen - 1; } /* write the size */ memcpy(buf->base + buf->off - body_size - sizelen, sizebuf, sizelen); return 0; } int ptls_buffer__adjust_asn1_blocksize(ptls_buffer_t *buf, size_t body_size) { fprintf(stderr, "unimplemented\n"); abort(); } int ptls_buffer_push_asn1_ubigint(ptls_buffer_t *buf, const void *bignum, size_t size) { const uint8_t *p = bignum, *const end = p + size; int ret; /* skip zeroes */ for (; end - p >= 1; ++p) if (*p != 0) break; /* emit */ ptls_buffer_push(buf, 2); ptls_buffer_push_asn1_block(buf, { if (*p >= 0x80) ptls_buffer_push(buf, 0); if (p != end) { ptls_buffer_pushv(buf, p, end - p); } else { ptls_buffer_pushv(buf, "", 1); } }); ret = 0; Exit: return ret; } #if PTLS_FUZZ_HANDSHAKE static size_t aead_encrypt(struct st_ptls_traffic_protection_t *ctx, void *output, const void *input, size_t inlen, uint8_t content_type) { memcpy(output, input, inlen); memcpy(output + inlen, &content_type, 1); return inlen + 1 + 16; } static int aead_decrypt(struct st_ptls_traffic_protection_t *ctx, void *output, size_t *outlen, const void *input, size_t inlen) { if (inlen < 16) { return PTLS_ALERT_BAD_RECORD_MAC; } memcpy(output, input, inlen - 16); *outlen = inlen - 16; /* removing the 16 bytes of tag */ return 0; } #else static void build_aad(uint8_t aad[5], size_t reclen) { aad[0] = PTLS_CONTENT_TYPE_APPDATA; aad[1] = PTLS_RECORD_VERSION_MAJOR; aad[2] = PTLS_RECORD_VERSION_MINOR; aad[3] = (uint8_t)(reclen >> 8); aad[4] = (uint8_t)reclen; } static size_t aead_encrypt(struct st_ptls_traffic_protection_t *ctx, void *output, const void *input, size_t inlen, uint8_t content_type) { ptls_iovec_t invec[2] = {ptls_iovec_init(input, inlen), ptls_iovec_init(&content_type, 1)}; uint8_t aad[5]; build_aad(aad, inlen + 1 + ctx->aead->algo->tag_size); ptls_aead_encrypt_v(ctx->aead, output, invec, PTLS_ELEMENTSOF(invec), ctx->seq++, aad, sizeof(aad)); return inlen + 1 + ctx->aead->algo->tag_size; } static int aead_decrypt(struct st_ptls_traffic_protection_t *ctx, void *output, size_t *outlen, const void *input, size_t inlen) { uint8_t aad[5]; build_aad(aad, inlen); if ((*outlen = ptls_aead_decrypt(ctx->aead, output, input, inlen, ctx->seq, aad, sizeof(aad))) == SIZE_MAX) return PTLS_ALERT_BAD_RECORD_MAC; ++ctx->seq; return 0; } #endif /* #if PTLS_FUZZ_HANDSHAKE */ static void build_tls12_aad(uint8_t *aad, uint8_t type, uint64_t seq, uint16_t length) { for (size_t i = 0; i < 8; ++i) aad[i] = (uint8_t)(seq >> (56 - i * 8)); aad[8] = type; aad[9] = PTLS_RECORD_VERSION_MAJOR; aad[10] = PTLS_RECORD_VERSION_MINOR; aad[11] = length >> 8; aad[12] = (uint8_t)length; } #define buffer_push_record(buf, type, block) \ do { \ ptls_buffer_push((buf), (type), PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR); \ ptls_buffer_push_block((buf), 2, block); \ } while (0) static int buffer_push_encrypted_records(ptls_buffer_t *buf, uint8_t type, const uint8_t *src, size_t len, struct st_ptls_traffic_protection_t *enc) { int ret = 0; while (len != 0) { size_t chunk_size = len; if (chunk_size > PTLS_MAX_PLAINTEXT_RECORD_SIZE) chunk_size = PTLS_MAX_PLAINTEXT_RECORD_SIZE; if (enc->tls12) { buffer_push_record(buf, type, { /* reserve memory */ if ((ret = ptls_buffer_reserve_aligned( buf, enc->aead->algo->tls12.record_iv_size + chunk_size + enc->aead->algo->tag_size, enc->aead->algo->align_bits)) != 0) goto Exit; /* determine nonce, as well as prepending that walue as the record IV (AES-GCM) */ uint64_t nonce; if (enc->aead->algo->tls12.record_iv_size != 0) { assert(enc->aead->algo->tls12.record_iv_size == 8); nonce = enc->tls12_enc_record_iv++; encode64(buf->base + buf->off, nonce); buf->off += 8; } else { nonce = enc->seq; } /* build AAD */ uint8_t aad[PTLS_TLS12_AAD_SIZE]; build_tls12_aad(aad, type, enc->seq, (uint16_t)chunk_size); /* encrypt */ buf->off += ptls_aead_encrypt(enc->aead, buf->base + buf->off, src, chunk_size, nonce, aad, sizeof(aad)); ++enc->seq; }); } else { buffer_push_record(buf, PTLS_CONTENT_TYPE_APPDATA, { if ((ret = ptls_buffer_reserve_aligned(buf, chunk_size + enc->aead->algo->tag_size + 1, enc->aead->algo->align_bits)) != 0) goto Exit; buf->off += aead_encrypt(enc, buf->base + buf->off, src, chunk_size, type); }); } src += chunk_size; len -= chunk_size; } Exit: return ret; } static int buffer_encrypt_record(ptls_buffer_t *buf, size_t rec_start, struct st_ptls_traffic_protection_t *enc) { size_t bodylen = buf->off - rec_start - 5; uint8_t *tmpbuf, type = buf->base[rec_start]; int ret; /* Fast path: do in-place encryption if only one record needs to be emitted. (For simplicity, do not take this path if TLS 1.2 * is used, as this function will be called no more than once per connection, for encrypting an alert.) */ if (!enc->tls12 && bodylen <= PTLS_MAX_PLAINTEXT_RECORD_SIZE) { size_t overhead = 1 + enc->aead->algo->tag_size; if ((ret = ptls_buffer_reserve_aligned(buf, overhead, enc->aead->algo->align_bits)) != 0) return ret; size_t encrypted_len = aead_encrypt(enc, buf->base + rec_start + 5, buf->base + rec_start + 5, bodylen, type); assert(encrypted_len == bodylen + overhead); buf->off += overhead; buf->base[rec_start] = PTLS_CONTENT_TYPE_APPDATA; buf->base[rec_start + 3] = (encrypted_len >> 8) & 0xff; buf->base[rec_start + 4] = encrypted_len & 0xff; return 0; } /* move plaintext to temporary buffer */ if ((tmpbuf = malloc(bodylen)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } memcpy(tmpbuf, buf->base + rec_start + 5, bodylen); ptls_clear_memory(buf->base + rec_start, bodylen + 5); buf->off = rec_start; /* push encrypted records */ ret = buffer_push_encrypted_records(buf, type, tmpbuf, bodylen, enc); Exit: if (tmpbuf != NULL) { ptls_clear_memory(tmpbuf, bodylen); free(tmpbuf); } return ret; } static int begin_record_message(ptls_message_emitter_t *_self) { struct st_ptls_record_message_emitter_t *self = (void *)_self; int ret; self->rec_start = self->super.buf->off; ptls_buffer_push(self->super.buf, PTLS_CONTENT_TYPE_HANDSHAKE, PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR, 0, 0); ret = 0; Exit: return ret; } static int commit_record_message(ptls_message_emitter_t *_self) { struct st_ptls_record_message_emitter_t *self = (void *)_self; int ret; if (self->super.enc->aead != NULL) { ret = buffer_encrypt_record(self->super.buf, self->rec_start, self->super.enc); } else { /* TODO allow CH,SH,HRR above 16KB */ size_t sz = self->super.buf->off - self->rec_start - 5; assert(sz <= PTLS_MAX_PLAINTEXT_RECORD_SIZE); self->super.buf->base[self->rec_start + 3] = (uint8_t)(sz >> 8); self->super.buf->base[self->rec_start + 4] = (uint8_t)(sz); ret = 0; } return ret; } #define buffer_push_extension(buf, type, block) \ do { \ ptls_buffer_push16((buf), (type)); \ ptls_buffer_push_block((buf), 2, block); \ } while (0); #define decode_open_extensions(src, end, hstype, exttype, block) \ do { \ struct st_ptls_extension_bitmap_t bitmap = {0}; \ ptls_decode_open_block((src), end, 2, { \ while ((src) != end) { \ if ((ret = ptls_decode16((exttype), &(src), end)) != 0) \ goto Exit; \ if (!extension_bitmap_testandset(&bitmap, (hstype), *(exttype))) { \ ret = PTLS_ALERT_ILLEGAL_PARAMETER; \ goto Exit; \ } \ ptls_decode_open_block((src), end, 2, block); \ } \ }); \ } while (0) #define decode_extensions(src, end, hstype, exttype, block) \ do { \ decode_open_extensions((src), end, hstype, exttype, block); \ ptls_decode_assert_block_close((src), end); \ } while (0) int ptls_decode8(uint8_t *value, const uint8_t **src, const uint8_t *end) { if (*src == end) return PTLS_ALERT_DECODE_ERROR; *value = *(*src)++; return 0; } int ptls_decode16(uint16_t *value, const uint8_t **src, const uint8_t *end) { if (end - *src < 2) return PTLS_ALERT_DECODE_ERROR; *value = ntoh16(*src); *src += 2; return 0; } int ptls_decode24(uint32_t *value, const uint8_t **src, const uint8_t *end) { if (end - *src < 3) return PTLS_ALERT_DECODE_ERROR; *value = ((uint32_t)(*src)[0] << 16) | ((uint32_t)(*src)[1] << 8) | (*src)[2]; *src += 3; return 0; } int ptls_decode32(uint32_t *value, const uint8_t **src, const uint8_t *end) { if (end - *src < 4) return PTLS_ALERT_DECODE_ERROR; *value = ntoh32(*src); *src += 4; return 0; } int ptls_decode64(uint64_t *value, const uint8_t **src, const uint8_t *end) { if (end - *src < 8) return PTLS_ALERT_DECODE_ERROR; *value = ntoh64(*src); *src += 8; return 0; } uint64_t ptls_decode_quicint(const uint8_t **src, const uint8_t *end) { if (PTLS_UNLIKELY(*src == end)) return UINT64_MAX; uint8_t b = *(*src)++; if (PTLS_LIKELY(b <= 0x3f)) return b; uint64_t v = b & 0x3f; unsigned bytes_left = (1 << (b >> 6)) - 1; if (PTLS_UNLIKELY((size_t)(end - *src) < bytes_left)) return UINT64_MAX; do { v = (v << 8) | *(*src)++; } while (--bytes_left != 0); return v; } static void log_secret(ptls_t *tls, const char *type, ptls_iovec_t secret) { char hexbuf[PTLS_MAX_DIGEST_SIZE * 2 + 1]; PTLS_PROBE(NEW_SECRET, tls, type, ptls_hexdump(hexbuf, secret.base, secret.len)); PTLS_LOG_CONN(new_secret, tls, { PTLS_LOG_ELEMENT_SAFESTR(label, type); }); if (tls->ctx->log_event != NULL) tls->ctx->log_event->cb(tls->ctx->log_event, tls, type, "%s", ptls_hexdump(hexbuf, secret.base, secret.len)); } /** * This function preserves the flags and modes (e.g., `offered`, `accepted`, `cipher`), they can be used afterwards. */ static void clear_ech(struct st_ptls_ech_t *ech, int is_server) { if (ech->aead != NULL) { ptls_aead_free(ech->aead); ech->aead = NULL; } ptls_clear_memory(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE); if (!is_server) { free(ech->client.enc.base); ech->client.enc = ptls_iovec_init(NULL, 0); if (ech->client.public_name != NULL) { free(ech->client.public_name); ech->client.public_name = NULL; } free(ech->client.first_ech.base); ech->client.first_ech = ptls_iovec_init(NULL, 0); } } /** * Decodes one ECHConfigContents (tls-esni-15 section 4). `decoded->kem` and `cipher` may be NULL even when the function returns * zero, if the corresponding entries are not found. */ static int decode_one_ech_config(ptls_hpke_kem_t **kems, ptls_hpke_cipher_suite_t **ciphers, struct st_decoded_ech_config_t *decoded, const uint8_t **src, const uint8_t *const end) { char *public_name_buf = NULL; int ret; *decoded = (struct st_decoded_ech_config_t){0}; if ((ret = ptls_decode8(&decoded->id, src, end)) != 0) goto Exit; uint16_t kem_id; if ((ret = ptls_decode16(&kem_id, src, end)) != 0) goto Exit; for (size_t i = 0; kems[i] != NULL; ++i) { if (kems[i]->id == kem_id) { decoded->kem = kems[i]; break; } } ptls_decode_open_block(*src, end, 2, { if (*src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } decoded->public_key = ptls_iovec_init(*src, end - *src); *src = end; }); ptls_decode_open_block(*src, end, 2, { do { uint16_t kdf_id; uint16_t aead_id; if ((ret = ptls_decode16(&kdf_id, src, end)) != 0) goto Exit; if ((ret = ptls_decode16(&aead_id, src, end)) != 0) goto Exit; if (decoded->cipher == NULL) { for (size_t i = 0; ciphers[i] != NULL; ++i) { if (ciphers[i]->id.kdf == kdf_id && ciphers[i]->id.aead == aead_id) { decoded->cipher = ciphers[i]; break; } } } } while (*src != end); }); if ((ret = ptls_decode8(&decoded->max_name_length, src, end)) != 0) goto Exit; #define SKIP_DECODED() \ do { \ decoded->kem = NULL; \ decoded->cipher = NULL; \ } while (0) /* Decode public_name. The specification requires clients to ignore (upon parsing ESNIConfigList) or reject (upon handshake) * public names that are not DNS names or IPv4 addresses. We ignore IPv4 and v6 addresses during parsing (IPv6 addresses never * looks like DNS names), and delegate the responsibility of rejecting non-DNS names to the certificate verify callback. */ ptls_decode_open_block(*src, end, 1, { if (*src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if ((public_name_buf = duplicate_as_str(*src, end - *src)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } if (ptls_server_name_is_ipaddr(public_name_buf)) { SKIP_DECODED(); } else { decoded->public_name = ptls_iovec_init(*src, end - *src); } *src = end; }); ptls_decode_block(*src, end, 2, { while (*src < end) { uint16_t type; if ((ret = ptls_decode16(&type, src, end)) != 0) goto Exit; ptls_decode_open_block(*src, end, 2, { *src = end; }); /* if a critital extension is found, indicate that the config cannot be used */ if ((type & 0x8000) != 0) SKIP_DECODED(); } }); #undef SKIP_DECODED Exit: free(public_name_buf); return ret; } static int client_decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list) { const uint8_t *src = config_list.base, *const end = src + config_list.len; int match_found = 0, ret; *decoded = (struct st_decoded_ech_config_t){0}; ptls_decode_block(src, end, 2, { do { const uint8_t *config_start = src; uint16_t version; if ((ret = ptls_decode16(&version, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 2, { /* If the block is the one that we recognize, parse it, then adopt if if possible. Otherwise, skip. */ if (version == PTLS_ECH_CONFIG_VERSION) { struct st_decoded_ech_config_t thisconf; if ((ret = decode_one_ech_config(ctx->ech.client.kems, ctx->ech.client.ciphers, &thisconf, &src, end)) != 0) goto Exit; if (!match_found && thisconf.kem != NULL && thisconf.cipher != NULL) { *decoded = thisconf; decoded->bytes = ptls_iovec_init(config_start, end - config_start); match_found = 1; } } else { src = end; } }); } while (src != end); }); ret = 0; Exit: if (ret != 0) *decoded = (struct st_decoded_ech_config_t){0}; return ret; } static int client_setup_ech(struct st_ptls_ech_t *ech, struct st_decoded_ech_config_t *decoded, void (*random_bytes)(void *, size_t)) { ptls_buffer_t infobuf; uint8_t infobuf_smallbuf[256]; int ret; /* setup `enc` and `aead` by running HPKE */ ptls_buffer_init(&infobuf, infobuf_smallbuf, sizeof(infobuf_smallbuf)); ptls_buffer_pushv(&infobuf, ech_info_prefix, sizeof(ech_info_prefix)); ptls_buffer_pushv(&infobuf, decoded->bytes.base, decoded->bytes.len); /* if ((ret = ptls_hpke_setup_base_s(decoded->kem, decoded->cipher, &ech->client.enc, &ech->aead, decoded->public_key, ptls_iovec_init(infobuf.base, infobuf.off))) != 0) goto Exit; */ /* setup the rest */ ech->config_id = decoded->id; ech->kem = decoded->kem; ech->cipher = decoded->cipher; random_bytes(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE); ech->client.max_name_length = decoded->max_name_length; if ((ech->client.public_name = duplicate_as_str(decoded->public_name.base, decoded->public_name.len)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } Exit: if (ret != 0) clear_ech(ech, 0); return ret; } static void client_setup_ech_grease(struct st_ptls_ech_t *ech, void (*random_bytes)(void *, size_t), ptls_hpke_kem_t **kems, ptls_hpke_cipher_suite_t **ciphers, const char *sni_name) { static const size_t x25519_key_size = 32; uint8_t random_secret[PTLS_AES128_KEY_SIZE + PTLS_AES_IV_SIZE]; /* pick up X25519, AES-128-GCM or bail out */ for (size_t i = 0; kems[i] != NULL; ++i) { if (kems[i]->id == PTLS_HPKE_KEM_X25519_SHA256) { ech->kem = kems[i]; break; } } for (size_t i = 0; ciphers[i] != NULL; ++i) { if (ciphers[i]->id.kdf == PTLS_HPKE_HKDF_SHA256 && ciphers[i]->id.aead == PTLS_HPKE_AEAD_AES_128_GCM) { ech->cipher = ciphers[i]; break; } } if (ech->kem == NULL || ech->cipher == NULL) goto Fail; /* aead is generated from random */ random_bytes(random_secret, sizeof(random_secret)); ech->aead = ptls_aead_new_direct(ech->cipher->aead, 1, random_secret, random_secret + PTLS_AES128_KEY_SIZE); /* `enc` is random bytes */ if ((ech->client.enc.base = malloc(x25519_key_size)) == NULL) goto Fail; ech->client.enc.len = x25519_key_size; random_bytes(ech->client.enc.base, ech->client.enc.len); /* setup the rest (inner_client_random is left zeros) */ random_bytes(&ech->config_id, sizeof(ech->config_id)); ech->client.max_name_length = 64; if ((ech->client.public_name = duplicate_as_str(sni_name, strlen(sni_name))) == NULL) goto Fail; return; Fail: clear_ech(ech, 0); } #define ECH_CONFIRMATION_SERVER_HELLO "ech accept confirmation" #define ECH_CONFIRMATION_HRR "hrr ech accept confirmation" static int ech_calc_confirmation(ptls_key_schedule_t *sched, void *dst, const uint8_t *inner_random, const char *label, ptls_iovec_t message) { ptls_hash_context_t *hash = NULL; uint8_t secret[PTLS_MAX_DIGEST_SIZE], transcript_hash[PTLS_MAX_DIGEST_SIZE]; int ret; /* calc transcript hash using the modified ServerHello / HRR */ if ((hash = sched->hashes[0].ctx->clone_(sched->hashes[0].ctx)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } hash->update(hash, message.base, message.len); hash->final(hash, transcript_hash, PTLS_HASH_FINAL_MODE_FREE); hash = NULL; /* HKDF extract and expand */ if ((ret = ptls_hkdf_extract(sched->hashes[0].algo, secret, ptls_iovec_init(NULL, 0), ptls_iovec_init(inner_random, PTLS_HELLO_RANDOM_SIZE))) != 0) goto Exit; if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, dst, 8, ptls_iovec_init(secret, sched->hashes[0].algo->digest_size), label, ptls_iovec_init(transcript_hash, sched->hashes[0].algo->digest_size), NULL)) != 0) goto Exit; Exit: ptls_clear_memory(secret, sizeof(secret)); ptls_clear_memory(transcript_hash, sizeof(transcript_hash)); if (hash != NULL) hash->final(hash, NULL, PTLS_HASH_FINAL_MODE_FREE); return ret; } static void key_schedule_free(ptls_key_schedule_t *sched) { size_t i; ptls_clear_memory(sched->secret, sizeof(sched->secret)); for (i = 0; i != sched->num_hashes; ++i) { sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE); if (sched->hashes[i].ctx_outer != NULL) sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE); } free(sched); } static ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered, int use_outer) { #define FOREACH_HASH(block) \ do { \ ptls_cipher_suite_t *cs; \ if ((cs = preferred) != NULL) { \ block \ } \ if (offered != NULL) { \ size_t i, j; \ for (i = 0; (cs = offered[i]) != NULL; ++i) { \ if (preferred == NULL || cs->hash != preferred->hash) { \ for (j = 0; j != i; ++j) \ if (cs->hash == offered[j]->hash) \ break; \ if (j == i) { \ block \ } \ } \ } \ } \ } while (0) ptls_key_schedule_t *sched; { /* allocate */ size_t num_hashes = 0; FOREACH_HASH({ ++num_hashes; }); if ((sched = malloc(offsetof(ptls_key_schedule_t, hashes) + sizeof(sched->hashes[0]) * num_hashes)) == NULL) return NULL; *sched = (ptls_key_schedule_t){0}; } /* setup the hash algos and contexts */ FOREACH_HASH({ sched->hashes[sched->num_hashes].algo = cs->hash; if ((sched->hashes[sched->num_hashes].ctx = cs->hash->create()) == NULL) goto Fail; if (use_outer) { if ((sched->hashes[sched->num_hashes].ctx_outer = cs->hash->create()) == NULL) goto Fail; } else { sched->hashes[sched->num_hashes].ctx_outer = NULL; } ++sched->num_hashes; }); return sched; Fail: key_schedule_free(sched); return NULL; #undef FOREACH_HASH } static int key_schedule_extract(ptls_key_schedule_t *sched, ptls_iovec_t ikm) { int ret; if (ikm.base == NULL) ikm = ptls_iovec_init(zeroes_of_max_digest_size, sched->hashes[0].algo->digest_size); if (sched->generation != 0 && (ret = ptls_hkdf_expand_label(sched->hashes[0].algo, sched->secret, sched->hashes[0].algo->digest_size, ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), "derived", ptls_iovec_init(sched->hashes[0].algo->empty_digest, sched->hashes[0].algo->digest_size), NULL)) != 0) return ret; ++sched->generation; ret = ptls_hkdf_extract(sched->hashes[0].algo, sched->secret, ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), ikm); PTLS_DEBUGF("%s: %u, %02x%02x\n", __FUNCTION__, sched->generation, (int)sched->secret[0], (int)sched->secret[1]); return ret; } static int key_schedule_select_cipher(ptls_key_schedule_t *sched, ptls_cipher_suite_t *cs, int reset) { size_t found_slot = SIZE_MAX, i; int ret; assert(sched->generation == 1); /* find the one, while freeing others */ for (i = 0; i != sched->num_hashes; ++i) { if (sched->hashes[i].algo == cs->hash) { assert(found_slot == SIZE_MAX); found_slot = i; } else { sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE); if (sched->hashes[i].ctx_outer != NULL) sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE); } } if (found_slot != 0) { sched->hashes[0] = sched->hashes[found_slot]; reset = 1; } sched->num_hashes = 1; /* recalculate the hash if a different hash as been selected than the one we used for calculating the early secrets */ if (reset) { --sched->generation; memset(sched->secret, 0, sizeof(sched->secret)); if ((ret = key_schedule_extract(sched, ptls_iovec_init(NULL, 0))) != 0) goto Exit; } ret = 0; Exit: return ret; } static void key_schedule_select_outer(ptls_key_schedule_t *sched) { /* This function is called when receiving a cleartext message (Server Hello), after the cipher-suite is determined (and hence * the hash also), if ECH was offered */ assert(sched->generation == 1); assert(sched->num_hashes == 1); assert(sched->hashes[0].ctx_outer != NULL); sched->hashes[0].ctx->final(sched->hashes[0].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE); sched->hashes[0].ctx = sched->hashes[0].ctx_outer; sched->hashes[0].ctx_outer = NULL; } void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen, int use_outer) { size_t i; PTLS_DEBUGF("%s:%zu\n", __FUNCTION__, msglen); for (i = 0; i != sched->num_hashes; ++i) { ptls_hash_context_t *ctx = use_outer ? sched->hashes[i].ctx_outer : sched->hashes[i].ctx; ctx->update(ctx, msg, msglen); } } static void key_schedule_update_ch1hash_prefix(ptls_key_schedule_t *sched) { uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)sched->hashes[0].algo->digest_size}; ptls__key_schedule_update_hash(sched, prefix, sizeof(prefix), 0); } static void key_schedule_extract_ch1hash(ptls_key_schedule_t *sched, uint8_t *hash) { assert(sched->hashes[0].ctx_outer == NULL); sched->hashes[0].ctx->final(sched->hashes[0].ctx, hash, PTLS_HASH_FINAL_MODE_RESET); } static void key_schedule_transform_post_ch1hash(ptls_key_schedule_t *sched) { size_t digest_size = sched->hashes[0].algo->digest_size; ptls_hash_context_t *hashes[3] = {sched->hashes[0].ctx, sched->hashes[0].ctx_outer, NULL}; uint8_t ch1hash[PTLS_MAX_DIGEST_SIZE]; uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)digest_size}; for (size_t i = 0; hashes[i] != NULL; ++i) { hashes[i]->final(hashes[i], ch1hash, PTLS_HASH_FINAL_MODE_RESET); hashes[i]->update(hashes[i], prefix, sizeof(prefix)); hashes[i]->update(hashes[i], ch1hash, digest_size); } ptls_clear_memory(ch1hash, sizeof(ch1hash)); } static int derive_secret_with_hash(ptls_key_schedule_t *sched, void *secret, const char *label, const uint8_t *hash) { int ret = ptls_hkdf_expand_label(sched->hashes[0].algo, secret, sched->hashes[0].algo->digest_size, ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), label, ptls_iovec_init(hash, sched->hashes[0].algo->digest_size), NULL); PTLS_DEBUGF("%s: (label=%s, hash=%02x%02x) => %02x%02x\n", __FUNCTION__, label, hash[0], hash[1], ((uint8_t *)secret)[0], ((uint8_t *)secret)[1]); return ret; } static int derive_secret(ptls_key_schedule_t *sched, void *secret, const char *label) { uint8_t hash_value[PTLS_MAX_DIGEST_SIZE]; sched->hashes[0].ctx->final(sched->hashes[0].ctx, hash_value, PTLS_HASH_FINAL_MODE_SNAPSHOT); int ret = derive_secret_with_hash(sched, secret, label, hash_value); ptls_clear_memory(hash_value, sizeof(hash_value)); return ret; } static int derive_secret_with_empty_digest(ptls_key_schedule_t *sched, void *secret, const char *label) { return derive_secret_with_hash(sched, secret, label, sched->hashes[0].algo->empty_digest); } static int derive_exporter_secret(ptls_t *tls, int is_early) { int ret; if (!tls->ctx->use_exporter) return 0; uint8_t **slot = is_early ? &tls->exporter_master_secret.early : &tls->exporter_master_secret.one_rtt; assert(*slot == NULL); if ((*slot = malloc(tls->key_schedule->hashes[0].algo->digest_size)) == NULL) return PTLS_ERROR_NO_MEMORY; if ((ret = derive_secret(tls->key_schedule, *slot, is_early ? "e exp master" : "exp master")) != 0) return ret; log_secret(tls, is_early ? "EARLY_EXPORTER_SECRET" : "EXPORTER_SECRET", ptls_iovec_init(*slot, tls->key_schedule->hashes[0].algo->digest_size)); return 0; } static void free_exporter_master_secret(ptls_t *tls, int is_early) { uint8_t *slot = is_early ? tls->exporter_master_secret.early : tls->exporter_master_secret.one_rtt; if (slot == NULL) return; assert(tls->key_schedule != NULL); ptls_clear_memory(slot, tls->key_schedule->hashes[0].algo->digest_size); free(slot); } static int derive_resumption_secret(ptls_key_schedule_t *sched, uint8_t *secret, ptls_iovec_t nonce) { int ret; if ((ret = derive_secret(sched, secret, "res master")) != 0) goto Exit; if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, secret, sched->hashes[0].algo->digest_size, ptls_iovec_init(secret, sched->hashes[0].algo->digest_size), "resumption", nonce, NULL)) != 0) goto Exit; Exit: if (ret != 0) ptls_clear_memory(secret, sched->hashes[0].algo->digest_size); return ret; } static int decode_new_session_ticket(ptls_t *tls, uint32_t *lifetime, uint32_t *age_add, ptls_iovec_t *nonce, ptls_iovec_t *ticket, uint32_t *max_early_data_size, const uint8_t *src, const uint8_t *const end) { uint16_t exttype; int ret; if ((ret = ptls_decode32(lifetime, &src, end)) != 0) goto Exit; if ((ret = ptls_decode32(age_add, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 1, { *nonce = ptls_iovec_init(src, end - src); src = end; }); ptls_decode_open_block(src, end, 2, { if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } *ticket = ptls_iovec_init(src, end - src); src = end; }); *max_early_data_size = 0; decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, &exttype, { if (tls->ctx->on_extension != NULL && (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, exttype, ptls_iovec_init(src, end - src)) != 0)) goto Exit; switch (exttype) { case PTLS_EXTENSION_TYPE_EARLY_DATA: if ((ret = ptls_decode32(max_early_data_size, &src, end)) != 0) goto Exit; break; default: src = end; break; } }); ret = 0; Exit: return ret; } static int decode_stored_session_ticket(ptls_t *tls, ptls_key_exchange_algorithm_t **key_share, ptls_cipher_suite_t **cs, ptls_iovec_t *secret, uint32_t *obfuscated_ticket_age, ptls_iovec_t *ticket, uint32_t *max_early_data_size, const uint8_t *src, const uint8_t *const end) { uint16_t kxid, csid; uint32_t lifetime, age_add; uint64_t obtained_at, now; ptls_iovec_t nonce; int ret; /* decode */ if ((ret = ptls_decode64(&obtained_at, &src, end)) != 0) goto Exit; if ((ret = ptls_decode16(&kxid, &src, end)) != 0) goto Exit; if ((ret = ptls_decode16(&csid, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 3, { if ((ret = decode_new_session_ticket(tls, &lifetime, &age_add, &nonce, ticket, max_early_data_size, src, end)) != 0) goto Exit; src = end; }); ptls_decode_block(src, end, 2, { *secret = ptls_iovec_init(src, end - src); src = end; }); { /* determine the key-exchange */ ptls_key_exchange_algorithm_t **cand; for (cand = tls->ctx->key_exchanges; *cand != NULL; ++cand) if ((*cand)->id == kxid) break; if (*cand == NULL) { ret = PTLS_ERROR_LIBRARY; goto Exit; } *key_share = *cand; } { /* determine the cipher-suite */ ptls_cipher_suite_t **cand; for (cand = tls->ctx->cipher_suites; *cand != NULL; ++cand) if ((*cand)->id == csid) break; if (*cand == NULL) { ret = PTLS_ERROR_LIBRARY; goto Exit; } *cs = *cand; } /* calculate obfuscated_ticket_age */ now = tls->ctx->get_time->cb(tls->ctx->get_time); if (!(obtained_at <= now && now - obtained_at < 7 * 86400 * 1000)) { ret = PTLS_ERROR_LIBRARY; goto Exit; } *obfuscated_ticket_age = (uint32_t)(now - obtained_at) + age_add; ret = 0; Exit: return ret; } static int get_traffic_key(ptls_hash_algorithm_t *algo, void *key, size_t key_size, int is_iv, const void *secret, ptls_iovec_t hash_value, const char *label_prefix) { return ptls_hkdf_expand_label(algo, key, key_size, ptls_iovec_init(secret, algo->digest_size), is_iv ? "iv" : "key", hash_value, label_prefix); } static int get_traffic_keys(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, void *key, void *iv, const void *secret, ptls_iovec_t hash_value, const char *label_prefix) { int ret; if ((ret = get_traffic_key(hash, key, aead->key_size, 0, secret, hash_value, label_prefix)) != 0 || (ret = get_traffic_key(hash, iv, aead->iv_size, 1, secret, hash_value, label_prefix)) != 0) { ptls_clear_memory(key, aead->key_size); ptls_clear_memory(iv, aead->iv_size); } return ret; } static int setup_traffic_protection(ptls_t *tls, int is_enc, const char *secret_label, size_t epoch, int skip_notify) { static const char *log_labels[2][4] = { {NULL, "CLIENT_EARLY_TRAFFIC_SECRET", "CLIENT_HANDSHAKE_TRAFFIC_SECRET", "CLIENT_TRAFFIC_SECRET_0"}, {NULL, NULL, "SERVER_HANDSHAKE_TRAFFIC_SECRET", "SERVER_TRAFFIC_SECRET_0"}}; struct st_ptls_traffic_protection_t *ctx = is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec; if (secret_label != NULL) { int ret; if ((ret = derive_secret(tls->key_schedule, ctx->secret, secret_label)) != 0) return ret; } ctx->epoch = epoch; log_secret(tls, log_labels[ptls_is_server(tls) == is_enc][epoch], ptls_iovec_init(ctx->secret, tls->key_schedule->hashes[0].algo->digest_size)); /* special path for applications having their own record layer */ if (tls->ctx->update_traffic_key != NULL) { if (skip_notify) return 0; return tls->ctx->update_traffic_key->cb(tls->ctx->update_traffic_key, tls, is_enc, epoch, ctx->secret); } if (ctx->aead != NULL) ptls_aead_free(ctx->aead); if ((ctx->aead = ptls_aead_new(tls->cipher_suite->aead, tls->cipher_suite->hash, is_enc, ctx->secret, tls->ctx->hkdf_label_prefix__obsolete)) == NULL) return PTLS_ERROR_NO_MEMORY; /* TODO obtain error from ptls_aead_new */ ctx->seq = 0; PTLS_DEBUGF("[%s] %02x%02x,%02x%02x\n", log_labels[ptls_is_server(tls)][epoch], (unsigned)ctx->secret[0], (unsigned)ctx->secret[1], (unsigned)ctx->aead->static_iv[0], (unsigned)ctx->aead->static_iv[1]); return 0; } static int commission_handshake_secret(ptls_t *tls) { int is_enc = !ptls_is_server(tls); assert(tls->pending_handshake_secret != NULL); memcpy((is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec)->secret, tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE); ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE); free(tls->pending_handshake_secret); tls->pending_handshake_secret = NULL; return setup_traffic_protection(tls, is_enc, NULL, 2, 1); } static void log_client_random(ptls_t *tls) { char buf[sizeof(tls->client_random) * 2 + 1]; PTLS_PROBE(CLIENT_RANDOM, tls, ptls_hexdump(buf, tls->client_random, sizeof(tls->client_random))); PTLS_LOG_CONN(client_random, tls, { PTLS_LOG_ELEMENT_HEXDUMP(bytes, tls->client_random, sizeof(tls->client_random)); }); } #define SESSION_IDENTIFIER_MAGIC "ptls0001" /* the number should be changed upon incompatible format change */ #define SESSION_IDENTIFIER_MAGIC_SIZE (sizeof(SESSION_IDENTIFIER_MAGIC) - 1) static int encode_session_identifier(ptls_context_t *ctx, ptls_buffer_t *buf, uint32_t ticket_age_add, ptls_iovec_t ticket_nonce, ptls_key_schedule_t *sched, const char *server_name, uint16_t key_exchange_id, uint16_t csid, const char *negotiated_protocol) { int ret = 0; ptls_buffer_push_block(buf, 2, { /* format id */ ptls_buffer_pushv(buf, SESSION_IDENTIFIER_MAGIC, SESSION_IDENTIFIER_MAGIC_SIZE); /* date */ ptls_buffer_push64(buf, ctx->get_time->cb(ctx->get_time)); /* resumption master secret */ ptls_buffer_push_block(buf, 2, { if ((ret = ptls_buffer_reserve(buf, sched->hashes[0].algo->digest_size)) != 0) goto Exit; if ((ret = derive_resumption_secret(sched, buf->base + buf->off, ticket_nonce)) != 0) goto Exit; buf->off += sched->hashes[0].algo->digest_size; }); /* key-exchange */ ptls_buffer_push16(buf, key_exchange_id); /* cipher-suite */ ptls_buffer_push16(buf, csid); /* ticket_age_add */ ptls_buffer_push32(buf, ticket_age_add); /* server-name */ ptls_buffer_push_block(buf, 2, { if (server_name != NULL) ptls_buffer_pushv(buf, server_name, strlen(server_name)); }); /* alpn */ ptls_buffer_push_block(buf, 1, { if (negotiated_protocol != NULL) ptls_buffer_pushv(buf, negotiated_protocol, strlen(negotiated_protocol)); }); }); Exit: return ret; } int decode_session_identifier(uint64_t *issued_at, ptls_iovec_t *psk, uint32_t *ticket_age_add, ptls_iovec_t *server_name, uint16_t *key_exchange_id, uint16_t *csid, ptls_iovec_t *negotiated_protocol, const uint8_t *src, const uint8_t *const end) { int ret = 0; ptls_decode_block(src, end, 2, { if (end - src < SESSION_IDENTIFIER_MAGIC_SIZE || memcmp(src, SESSION_IDENTIFIER_MAGIC, SESSION_IDENTIFIER_MAGIC_SIZE) != 0) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } src += SESSION_IDENTIFIER_MAGIC_SIZE; if ((ret = ptls_decode64(issued_at, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 2, { *psk = ptls_iovec_init(src, end - src); src = end; }); if ((ret = ptls_decode16(key_exchange_id, &src, end)) != 0) goto Exit; if ((ret = ptls_decode16(csid, &src, end)) != 0) goto Exit; if ((ret = ptls_decode32(ticket_age_add, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 2, { *server_name = ptls_iovec_init(src, end - src); src = end; }); ptls_decode_open_block(src, end, 1, { *negotiated_protocol = ptls_iovec_init(src, end - src); src = end; }); }); Exit: return ret; } static size_t build_certificate_verify_signdata(uint8_t *data, ptls_key_schedule_t *sched, const char *context_string) { size_t datalen = 0; memset(data + datalen, 32, 64); datalen += 64; memcpy(data + datalen, context_string, strlen(context_string) + 1); datalen += strlen(context_string) + 1; sched->hashes[0].ctx->final(sched->hashes[0].ctx, data + datalen, PTLS_HASH_FINAL_MODE_SNAPSHOT); datalen += sched->hashes[0].algo->digest_size; assert(datalen <= PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE); return datalen; } static int calc_verify_data(void *output, ptls_key_schedule_t *sched, const void *secret) { ptls_hash_context_t *hmac; uint8_t digest[PTLS_MAX_DIGEST_SIZE]; int ret; if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, digest, sched->hashes[0].algo->digest_size, ptls_iovec_init(secret, sched->hashes[0].algo->digest_size), "finished", ptls_iovec_init(NULL, 0), NULL)) != 0) return ret; if ((hmac = ptls_hmac_create(sched->hashes[0].algo, digest, sched->hashes[0].algo->digest_size)) == NULL) { ptls_clear_memory(digest, sizeof(digest)); return PTLS_ERROR_NO_MEMORY; } sched->hashes[0].ctx->final(sched->hashes[0].ctx, digest, PTLS_HASH_FINAL_MODE_SNAPSHOT); PTLS_DEBUGF("%s: %02x%02x,%02x%02x\n", __FUNCTION__, ((uint8_t *)secret)[0], ((uint8_t *)secret)[1], digest[0], digest[1]); hmac->update(hmac, digest, sched->hashes[0].algo->digest_size); ptls_clear_memory(digest, sizeof(digest)); hmac->final(hmac, output, PTLS_HASH_FINAL_MODE_FREE); return 0; } static int verify_finished(ptls_t *tls, ptls_iovec_t message) { uint8_t verify_data[PTLS_MAX_DIGEST_SIZE]; int ret; if (PTLS_HANDSHAKE_HEADER_SIZE + tls->key_schedule->hashes[0].algo->digest_size != message.len) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if ((ret = calc_verify_data(verify_data, tls->key_schedule, tls->traffic_protection.dec.secret)) != 0) goto Exit; if (!ptls_mem_equal(message.base + PTLS_HANDSHAKE_HEADER_SIZE, verify_data, tls->key_schedule->hashes[0].algo->digest_size)) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } Exit: ptls_clear_memory(verify_data, sizeof(verify_data)); return ret; } static int send_finished(ptls_t *tls, ptls_message_emitter_t *emitter) { int ret; ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, { if ((ret = ptls_buffer_reserve(emitter->buf, tls->key_schedule->hashes[0].algo->digest_size)) != 0) goto Exit; if ((ret = calc_verify_data(emitter->buf->base + emitter->buf->off, tls->key_schedule, tls->traffic_protection.enc.secret)) != 0) goto Exit; emitter->buf->off += tls->key_schedule->hashes[0].algo->digest_size; }); Exit: return ret; } static int send_session_ticket(ptls_t *tls, ptls_message_emitter_t *emitter) { ptls_hash_context_t *msghash_backup = tls->key_schedule->hashes[0].ctx->clone_(tls->key_schedule->hashes[0].ctx); ptls_buffer_t session_id; char session_id_smallbuf[128]; uint32_t ticket_age_add; int ret = 0; assert(tls->ctx->ticket_lifetime != 0); assert(tls->ctx->encrypt_ticket != NULL); ptls_buffer_init(&session_id, session_id_smallbuf, sizeof(session_id_smallbuf)); { /* calculate verify-data that will be sent by the client */ size_t orig_off = emitter->buf->off; if (tls->pending_handshake_secret != NULL && !tls->ctx->omit_end_of_early_data) { assert(tls->state == PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA); ptls_buffer_push_message_body(emitter->buf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA, {}); emitter->buf->off = orig_off; } ptls_buffer_push_message_body(emitter->buf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, { if ((ret = ptls_buffer_reserve(emitter->buf, tls->key_schedule->hashes[0].algo->digest_size)) != 0) goto Exit; if ((ret = calc_verify_data(emitter->buf->base + emitter->buf->off, tls->key_schedule, tls->pending_handshake_secret != NULL ? tls->pending_handshake_secret : tls->traffic_protection.dec.secret)) != 0) goto Exit; emitter->buf->off += tls->key_schedule->hashes[0].algo->digest_size; }); emitter->buf->off = orig_off; } tls->ctx->random_bytes(&ticket_age_add, sizeof(ticket_age_add)); /* build the raw nsk */ ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule, tls->server_name, tls->key_share->id, tls->cipher_suite->id, tls->negotiated_protocol); if (ret != 0) goto Exit; /* encrypt and send */ ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, { ptls_buffer_push32(emitter->buf, tls->ctx->ticket_lifetime); ptls_buffer_push32(emitter->buf, ticket_age_add); ptls_buffer_push_block(emitter->buf, 1, {}); ptls_buffer_push_block(emitter->buf, 2, { if ((ret = tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 1, emitter->buf, ptls_iovec_init(session_id.base, session_id.off))) != 0) goto Exit; }); ptls_buffer_push_block(emitter->buf, 2, { if (tls->ctx->max_early_data_size != 0) buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_EARLY_DATA, { ptls_buffer_push32(emitter->buf, tls->ctx->max_early_data_size); }); }); }); Exit: ptls_buffer_dispose(&session_id); /* restore handshake state */ tls->key_schedule->hashes[0].ctx->final(tls->key_schedule->hashes[0].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE); tls->key_schedule->hashes[0].ctx = msghash_backup; return ret; } static int push_change_cipher_spec(ptls_t *tls, ptls_message_emitter_t *emitter) { int ret; /* check if we are requested to (or still need to) */ if (!tls->send_change_cipher_spec) { ret = 0; goto Exit; } /* CCS is a record, can only be sent when using a record-based protocol. */ if (emitter->begin_message != begin_record_message) { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; goto Exit; } /* emit CCS */ buffer_push_record(emitter->buf, PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC, { ptls_buffer_push(emitter->buf, 1); }); tls->send_change_cipher_spec = 0; ret = 0; Exit: return ret; } static int push_additional_extensions(ptls_handshake_properties_t *properties, ptls_buffer_t *sendbuf) { int ret; if (properties != NULL && properties->additional_extensions != NULL) { ptls_raw_extension_t *ext; for (ext = properties->additional_extensions; ext->type != UINT16_MAX; ++ext) { buffer_push_extension(sendbuf, ext->type, { ptls_buffer_pushv(sendbuf, ext->data.base, ext->data.len); }); } } ret = 0; Exit: return ret; } static int push_signature_algorithms(ptls_verify_certificate_t *vc, ptls_buffer_t *sendbuf) { /* The list sent when verify callback is not registered */ static const uint16_t default_algos[] = {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256, PTLS_SIGNATURE_RSA_PKCS1_SHA256, PTLS_SIGNATURE_RSA_PKCS1_SHA1, UINT16_MAX}; int ret; ptls_buffer_push_block(sendbuf, 2, { for (const uint16_t *p = vc != NULL ? vc->algos : default_algos; *p != UINT16_MAX; ++p) ptls_buffer_push16(sendbuf, *p); }); ret = 0; Exit: return ret; } static int decode_signature_algorithms(struct st_ptls_signature_algorithms_t *sa, const uint8_t **src, const uint8_t *end) { int ret; ptls_decode_block(*src, end, 2, { do { uint16_t id; if ((ret = ptls_decode16(&id, src, end)) != 0) goto Exit; if (sa->count < PTLS_ELEMENTSOF(sa->list)) sa->list[sa->count++] = id; } while (*src != end); }); ret = 0; Exit: return ret; } static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **candidates, const uint8_t *src, const uint8_t *const end, int server_preference, int server_chacha_priority) { size_t found_index = SIZE_MAX; int ret; while (src != end) { uint16_t id; if ((ret = ptls_decode16(&id, &src, end)) != 0) goto Exit; for (size_t i = 0; candidates[i] != NULL; ++i) { if (candidates[i]->id == id) { if (server_preference && !(server_chacha_priority && id == PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)) { /* preserve smallest matching index, and proceed to the next input */ if (i < found_index) { found_index = i; break; } } else { /* return the pointer matching to the first input that can be used */ *selected = candidates[i]; goto Exit; } } } /* first position of the server list matched (server_preference) */ if (found_index == 0) break; /* server preference is overridden only if the first entry of client-provided list is chachapoly */ server_chacha_priority = 0; } if (found_index != SIZE_MAX) { *selected = candidates[found_index]; ret = 0; } else { ret = PTLS_ALERT_HANDSHAKE_FAILURE; } Exit: return ret; } static int push_key_share_entry(ptls_buffer_t *buf, uint16_t group, ptls_iovec_t pubkey) { int ret; ptls_buffer_push16(buf, group); ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, pubkey.base, pubkey.len); }); ret = 0; Exit: return ret; } static int decode_key_share_entry(uint16_t *group, ptls_iovec_t *key_exchange, const uint8_t **src, const uint8_t *const end) { int ret; if ((ret = ptls_decode16(group, src, end)) != 0) goto Exit; ptls_decode_open_block(*src, end, 2, { *key_exchange = ptls_iovec_init(*src, end - *src); *src = end; }); Exit: return ret; } static int select_key_share(ptls_key_exchange_algorithm_t **selected, ptls_iovec_t *peer_key, ptls_key_exchange_algorithm_t **candidates, const uint8_t **src, const uint8_t *const end, int expect_one) { int ret; *selected = NULL; if (expect_one && *src == end) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } while (*src != end) { uint16_t group; ptls_iovec_t key; if ((ret = decode_key_share_entry(&group, &key, src, end)) != 0) goto Exit; ptls_key_exchange_algorithm_t **c = candidates; for (; *c != NULL; ++c) { if (*selected == NULL && (*c)->id == group) { *selected = *c; *peer_key = key; } } if (expect_one) { ret = *selected != NULL ? 0 : PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } ret = 0; Exit: return ret; } static int emit_server_name_extension(ptls_buffer_t *buf, const char *server_name) { int ret; ptls_buffer_push_block(buf, 2, { ptls_buffer_push(buf, PTLS_SERVER_NAME_TYPE_HOSTNAME); ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, server_name, strlen(server_name)); }); }); ret = 0; Exit: return ret; } /** * Within the outer ECH extension, returns the number of bytes that preceeds the AEAD-encrypted payload. */ static inline size_t outer_ech_header_size(size_t enc_size) { return 10 + enc_size; } /** * Flag to indicate which of ClientHelloInner, EncodedClientHelloInner, ClientHelloOuter is to be generated. When ECH is inactive, * only ClientHelloInner is used. */ enum encode_ch_mode { ENCODE_CH_MODE_INNER, ENCODE_CH_MODE_ENCODED_INNER, ENCODE_CH_MODE_OUTER }; static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum encode_ch_mode mode, int is_second_flight, ptls_handshake_properties_t *properties, const void *client_random, ptls_key_exchange_context_t *key_share_ctx, const char *sni_name, ptls_iovec_t legacy_session_id, struct st_ptls_ech_t *ech, size_t *ech_size_offset, ptls_iovec_t ech_replay, ptls_iovec_t resumption_secret, ptls_iovec_t resumption_ticket, uint32_t obfuscated_ticket_age, size_t psk_binder_size, ptls_iovec_t *cookie, int using_early_data) { int ret; assert(mode == ENCODE_CH_MODE_INNER || ech != NULL); ptls_buffer_push_message_body(sendbuf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, { /* legacy_version */ ptls_buffer_push16(sendbuf, 0x0303); /* random_bytes */ ptls_buffer_pushv(sendbuf, client_random, PTLS_HELLO_RANDOM_SIZE); /* lecagy_session_id */ ptls_buffer_push_block(sendbuf, 1, { if (mode != ENCODE_CH_MODE_ENCODED_INNER) ptls_buffer_pushv(sendbuf, legacy_session_id.base, legacy_session_id.len); }); /* cipher_suites */ ptls_buffer_push_block(sendbuf, 2, { ptls_cipher_suite_t **cs = ctx->cipher_suites; for (; *cs != NULL; ++cs) ptls_buffer_push16(sendbuf, (*cs)->id); }); /* legacy_compression_methods */ ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, 0); }); /* extensions */ ptls_buffer_push_block(sendbuf, 2, { if (mode == ENCODE_CH_MODE_OUTER) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, { size_t ext_payload_from = sendbuf->off; ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_OUTER); ptls_buffer_push16(sendbuf, ech->cipher->id.kdf); ptls_buffer_push16(sendbuf, ech->cipher->id.aead); ptls_buffer_push(sendbuf, ech->config_id); ptls_buffer_push_block(sendbuf, 2, { if (!is_second_flight) ptls_buffer_pushv(sendbuf, ech->client.enc.base, ech->client.enc.len); }); ptls_buffer_push_block(sendbuf, 2, { assert(sendbuf->off - ext_payload_from == outer_ech_header_size(is_second_flight ? 0 : ech->client.enc.len)); if ((ret = ptls_buffer_reserve(sendbuf, *ech_size_offset)) != 0) goto Exit; memset(sendbuf->base + sendbuf->off, 0, *ech_size_offset); sendbuf->off += *ech_size_offset; *ech_size_offset = sendbuf->off - *ech_size_offset; }); }); } else if (ech->aead != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, { ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_INNER); }); } else if (ech_replay.base != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, { ptls_buffer_pushv(sendbuf, ech_replay.base, ech_replay.len); }); } if (mode == ENCODE_CH_MODE_ENCODED_INNER) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS, { ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push16(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE); }); }); } else { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, { ptls_buffer_push_block(sendbuf, 2, { if (key_share_ctx != NULL && (ret = push_key_share_entry(sendbuf, key_share_ctx->algo->id, key_share_ctx->pubkey)) != 0) goto Exit; }); }); } if (sni_name != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, { if ((ret = emit_server_name_extension(sendbuf, sni_name)) != 0) goto Exit; }); } if (properties != NULL && properties->client.negotiated_protocols.count != 0) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, { ptls_buffer_push_block(sendbuf, 2, { size_t i; for (i = 0; i != properties->client.negotiated_protocols.count; ++i) { ptls_buffer_push_block(sendbuf, 1, { ptls_iovec_t p = properties->client.negotiated_protocols.list[i]; ptls_buffer_pushv(sendbuf, p.base, p.len); }); } }); }); } if (ctx->decompress_certificate != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE, { ptls_buffer_push_block(sendbuf, 1, { const uint16_t *algo = ctx->decompress_certificate->supported_algorithms; assert(*algo != UINT16_MAX); for (; *algo != UINT16_MAX; ++algo) ptls_buffer_push16(sendbuf, *algo); }); }); } buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, { ptls_buffer_push_block(sendbuf, 1, { size_t i; for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i) ptls_buffer_push16(sendbuf, supported_versions[i]); }); }); buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, { if ((ret = push_signature_algorithms(ctx->verify_certificate, sendbuf)) != 0) goto Exit; }); buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, { ptls_key_exchange_algorithm_t **algo = ctx->key_exchanges; ptls_buffer_push_block(sendbuf, 2, { for (; *algo != NULL; ++algo) ptls_buffer_push16(sendbuf, (*algo)->id); }); }); if (cookie != NULL && cookie->base != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, { ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); }); }); } if (ctx->use_raw_public_keys) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, { ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); }); }); } if ((ret = push_additional_extensions(properties, sendbuf)) != 0) goto Exit; if (ctx->save_ticket != NULL || resumption_secret.base != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES, { ptls_buffer_push_block(sendbuf, 1, { if (!ctx->require_dhe_on_psk) ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK); ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK_DHE); }); }); } if (resumption_secret.base != NULL) { if (using_early_data && !is_second_flight) buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {}); /* pre-shared key "MUST be the last extension in the ClientHello" (draft-17 section 4.2.6) */ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, { ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_push_block(sendbuf, 2, { if (mode == ENCODE_CH_MODE_OUTER) { if ((ret = ptls_buffer_reserve(sendbuf, resumption_ticket.len)) != 0) goto Exit; ctx->random_bytes(sendbuf->base + sendbuf->off, resumption_ticket.len); sendbuf->off += resumption_ticket.len; } else { ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len); } }); uint32_t age; if (mode == ENCODE_CH_MODE_OUTER) { ctx->random_bytes(&age, sizeof(age)); } else { age = obfuscated_ticket_age; } ptls_buffer_push32(sendbuf, age); }); /* allocate space for PSK binder. The space is filled initially filled by a random value (meeting the * requirement of ClientHelloOuter), and later gets filled with the correct binder value if necessary. */ ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_push_block(sendbuf, 1, { if ((ret = ptls_buffer_reserve(sendbuf, psk_binder_size)) != 0) goto Exit; ctx->random_bytes(sendbuf->base + sendbuf->off, psk_binder_size); sendbuf->off += psk_binder_size; }); }); }); } }); }); Exit: return ret; } static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_handshake_properties_t *properties, ptls_iovec_t *cookie) { ptls_iovec_t resumption_secret = {NULL}, resumption_ticket = {NULL}; uint32_t obfuscated_ticket_age = 0; const char *sni_name = NULL; size_t mess_start, msghash_off; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; ptls_buffer_t encoded_ch_inner; int ret, is_second_flight = tls->key_schedule != NULL; ptls_buffer_init(&encoded_ch_inner, "", 0); if (tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name)) sni_name = tls->server_name; if (properties != NULL) { /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */ if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL) { if (properties->client.ech.configs.len != 0) { struct st_decoded_ech_config_t decoded; client_decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs); if (decoded.kem != NULL && decoded.cipher != NULL) { if ((ret = client_setup_ech(&tls->ech, &decoded, tls->ctx->random_bytes)) != 0) goto Exit; } } else { /* zero-length config indicates ECH greasing */ client_setup_ech_grease(&tls->ech, tls->ctx->random_bytes, tls->ctx->ech.client.kems, tls->ctx->ech.client.ciphers, sni_name); } } /* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */ if (properties->client.session_ticket.base != NULL) { ptls_key_exchange_algorithm_t *key_share = NULL; ptls_cipher_suite_t *cipher_suite = NULL; uint32_t max_early_data_size; if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &resumption_secret, &obfuscated_ticket_age, &resumption_ticket, &max_early_data_size, properties->client.session_ticket.base, properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) { tls->client.offered_psk = 1; /* key-share selected by HRR should not be overridden */ if (tls->key_share == NULL) tls->key_share = key_share; tls->cipher_suite = cipher_suite; if (!is_second_flight && max_early_data_size != 0 && properties->client.max_early_data_size != NULL) { tls->client.using_early_data = 1; *properties->client.max_early_data_size = max_early_data_size; } } else { resumption_secret = ptls_iovec_init(NULL, 0); } } if (tls->client.using_early_data) { properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; } else { if (properties->client.max_early_data_size != NULL) *properties->client.max_early_data_size = 0; properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED; } } /* use the default key share if still not undetermined */ if (tls->key_share == NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange)) tls->key_share = tls->ctx->key_exchanges[0]; /* instantiate key share context */ assert(tls->client.key_share_ctx == NULL); if (tls->key_share != NULL) { if ((ret = tls->key_share->create(tls->key_share, &tls->client.key_share_ctx)) != 0) goto Exit; } /* initialize key schedule */ if (!is_second_flight) { tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites, tls->ech.aead != NULL); if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0) goto Exit; } /* start generating CH */ if ((ret = emitter->begin_message(emitter)) != 0) goto Exit; mess_start = msghash_off = emitter->buf->off; /* generate true (inner) CH */ if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_INNER, is_second_flight, properties, tls->ech.aead != NULL ? tls->ech.inner_client_random : tls->client_random, tls->client.key_share_ctx, sni_name, tls->client.legacy_session_id, &tls->ech, NULL, tls->ech.client.first_ech, resumption_secret, resumption_ticket, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; /* update the message hash, filling in the PSK binder HMAC if necessary */ if (resumption_secret.base != NULL) { size_t psk_binder_off = emitter->buf->off - (3 + tls->key_schedule->hashes[0].algo->digest_size); if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, "res binder")) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off, 0); msghash_off = psk_binder_off; if ((ret = calc_verify_data(emitter->buf->base + psk_binder_off + 3, tls->key_schedule, binder_key)) != 0) goto Exit; } ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, emitter->buf->off - msghash_off, 0); /* ECH */ if (tls->ech.aead != NULL) { /* build EncodedCHInner */ if ((ret = encode_client_hello(tls->ctx, &encoded_ch_inner, ENCODE_CH_MODE_ENCODED_INNER, is_second_flight, properties, tls->ech.inner_client_random, tls->client.key_share_ctx, sni_name, tls->client.legacy_session_id, &tls->ech, NULL, ptls_iovec_init(NULL, 0), resumption_secret, resumption_ticket, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; if (resumption_secret.base != NULL) memcpy(encoded_ch_inner.base + encoded_ch_inner.off - tls->key_schedule->hashes[0].algo->digest_size, emitter->buf->base + emitter->buf->off - tls->key_schedule->hashes[0].algo->digest_size, tls->key_schedule->hashes[0].algo->digest_size); { /* pad EncodedCHInner (following draft-ietf-tls-esni-15 6.1.3) */ size_t padding_len; if (sni_name != NULL) { padding_len = strlen(sni_name); if (padding_len < tls->ech.client.max_name_length) padding_len = tls->ech.client.max_name_length; } else { padding_len = tls->ech.client.max_name_length + 9; } size_t final_len = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + padding_len; final_len = (final_len + 31) / 32 * 32; padding_len = final_len - (encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE); if (padding_len != 0) { if ((ret = ptls_buffer_reserve(&encoded_ch_inner, padding_len)) != 0) goto Exit; memset(encoded_ch_inner.base + encoded_ch_inner.off, 0, padding_len); encoded_ch_inner.off += padding_len; } } /* flush CHInner, build CHOuterAAD */ emitter->buf->off = mess_start; size_t ech_payload_size = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + tls->ech.aead->algo->tag_size, ech_size_offset = ech_payload_size; if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_OUTER, is_second_flight, properties, tls->client_random, tls->client.key_share_ctx, tls->ech.client.public_name, tls->client.legacy_session_id, &tls->ech, &ech_size_offset, ptls_iovec_init(NULL, 0), resumption_secret, resumption_ticket, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; /* overwrite ECH payload */ ptls_aead_encrypt(tls->ech.aead, emitter->buf->base + ech_size_offset, encoded_ch_inner.base + PTLS_HANDSHAKE_HEADER_SIZE, encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE, is_second_flight, emitter->buf->base + mess_start + PTLS_HANDSHAKE_HEADER_SIZE, emitter->buf->off - (mess_start + PTLS_HANDSHAKE_HEADER_SIZE)); /* keep the copy of the 1st ECH extension so that we can send it again in 2nd CH in response to rejection with HRR */ if (!is_second_flight) { size_t len = outer_ech_header_size(tls->ech.client.enc.len) + ech_payload_size; if ((tls->ech.client.first_ech.base = malloc(len)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } memcpy(tls->ech.client.first_ech.base, emitter->buf->base + ech_size_offset - outer_ech_header_size(tls->ech.client.enc.len), len); tls->ech.client.first_ech.len = len; if (properties->client.ech.configs.len != 0) { tls->ech.offered = 1; } else { tls->ech.offered_grease = 1; } } /* update hash */ ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + mess_start, emitter->buf->off - mess_start, 1); } /* commit CH to the record layer */ if ((ret = emitter->commit_message(emitter)) != 0) goto Exit; if (tls->client.using_early_data) { assert(!is_second_flight); if ((ret = setup_traffic_protection(tls, 1, "c e traffic", 1, 0)) != 0) goto Exit; if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; } if (resumption_secret.base != NULL && !is_second_flight) { if ((ret = derive_exporter_secret(tls, 1)) != 0) goto Exit; } tls->state = cookie == NULL ? PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO : PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO; ret = PTLS_ERROR_IN_PROGRESS; Exit: ptls_buffer_dispose(&encoded_ch_inner); ptls_clear_memory(binder_key, sizeof(binder_key)); return ret; } ptls_cipher_suite_t *ptls_find_cipher_suite(ptls_cipher_suite_t **cipher_suites, uint16_t id) { ptls_cipher_suite_t **cs; if (cipher_suites == NULL) return NULL; for (cs = cipher_suites; *cs != NULL && (*cs)->id != id; ++cs) ; return *cs; } static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, const uint8_t *src, const uint8_t *const end) { int ret; *sh = (struct st_ptls_server_hello_t){{0}}; /* ignore legacy-version */ if (end - src < 2) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } src += 2; /* random */ if (end - src < PTLS_HELLO_RANDOM_SIZE) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } sh->is_retry_request = memcmp(src, hello_retry_random, PTLS_HELLO_RANDOM_SIZE) == 0; src += PTLS_HELLO_RANDOM_SIZE; /* legacy_session_id */ ptls_decode_open_block(src, end, 1, { if (end - src > 32) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } sh->legacy_session_id = ptls_iovec_init(src, end - src); src = end; }); { /* select cipher_suite */ uint16_t csid; if ((ret = ptls_decode16(&csid, &src, end)) != 0) goto Exit; if ((tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid)) == NULL) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } { /* legacy_compression_method */ uint8_t method; if ((ret = ptls_decode8(&method, &src, end)) != 0) goto Exit; if (method != 0) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } if (sh->is_retry_request) sh->retry_request.selected_group = UINT16_MAX; uint16_t exttype, found_version = UINT16_MAX, selected_psk_identity = UINT16_MAX; decode_extensions(src, end, sh->is_retry_request ? PTLS_HANDSHAKE_TYPE_PSEUDO_HRR : PTLS_HANDSHAKE_TYPE_SERVER_HELLO, &exttype, { if (tls->ctx->on_extension != NULL && (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, exttype, ptls_iovec_init(src, end - src)) != 0)) goto Exit; switch (exttype) { case PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS: if ((ret = ptls_decode16(&found_version, &src, end)) != 0) goto Exit; break; case PTLS_EXTENSION_TYPE_KEY_SHARE: if (sh->is_retry_request) { if ((ret = ptls_decode16(&sh->retry_request.selected_group, &src, end)) != 0) goto Exit; } else { uint16_t group; if ((ret = decode_key_share_entry(&group, &sh->peerkey, &src, end)) != 0) goto Exit; if (src != end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if (tls->key_share == NULL || tls->key_share->id != group) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } break; case PTLS_EXTENSION_TYPE_COOKIE: assert(sh->is_retry_request); ptls_decode_block(src, end, 2, { if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } sh->retry_request.cookie = ptls_iovec_init(src, end - src); src = end; }); break; case PTLS_EXTENSION_TYPE_PRE_SHARED_KEY: assert(!sh->is_retry_request); if ((ret = ptls_decode16(&selected_psk_identity, &src, end)) != 0) goto Exit; break; case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO: assert(sh->is_retry_request); if (!(tls->ech.offered || tls->ech.offered_grease)) { ret = PTLS_ALERT_UNSUPPORTED_EXTENSION; goto Exit; } if (end - src != PTLS_ECH_CONFIRM_LENGTH) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } sh->retry_request.ech = src; src = end; break; default: src = end; break; } }); if (!is_supported_version(found_version)) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } if (!sh->is_retry_request) { if (selected_psk_identity != UINT16_MAX) { if (!tls->client.offered_psk) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } if (selected_psk_identity != 0) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } tls->is_psk_handshake = 1; } if (sh->peerkey.base == NULL && !tls->is_psk_handshake) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } ret = 0; Exit: return ret; } static int handle_hello_retry_request(ptls_t *tls, ptls_message_emitter_t *emitter, struct st_ptls_server_hello_t *sh, ptls_iovec_t message, ptls_handshake_properties_t *properties) { int ret; if (tls->client.key_share_ctx != NULL) { tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, 1, NULL, ptls_iovec_init(NULL, 0)); tls->client.key_share_ctx = NULL; } if (tls->client.using_early_data) { /* release traffic encryption key so that 2nd CH goes out in cleartext, but keep the epoch at 1 since we've already * called derive-secret */ if (tls->ctx->update_traffic_key == NULL) { assert(tls->traffic_protection.enc.aead != NULL); ptls_aead_free(tls->traffic_protection.enc.aead); tls->traffic_protection.enc.aead = NULL; } tls->client.using_early_data = 0; } if (sh->retry_request.selected_group != UINT16_MAX) { /* we offer the first key_exchanges[0] as KEY_SHARE unless client.negotiate_before_key_exchange is set */ ptls_key_exchange_algorithm_t **cand; for (cand = tls->ctx->key_exchanges; *cand != NULL; ++cand) if ((*cand)->id == sh->retry_request.selected_group) break; if (*cand == NULL) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } tls->key_share = *cand; } else if (tls->key_share != NULL) { /* retain the key-share using in first CH, if server does not specify one */ } else { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } ret = send_client_hello(tls, emitter, properties, &sh->retry_request.cookie); Exit: return ret; } static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t confirm_hash_off, const char *label) { uint8_t confirm_hash_delivered[PTLS_ECH_CONFIRM_LENGTH], confirm_hash_expected[PTLS_ECH_CONFIRM_LENGTH]; int ret = 0; /* Determine if ECH has been accepted by checking the confirmation hash. `confirm_hash_off` set to zero indicates that HRR was * received wo. ECH extension, which is an indication that ECH was rejected. */ if (confirm_hash_off != 0) { memcpy(confirm_hash_delivered, message.base + confirm_hash_off, sizeof(confirm_hash_delivered)); memset(message.base + confirm_hash_off, 0, sizeof(confirm_hash_delivered)); if ((ret = ech_calc_confirmation(tls->key_schedule, confirm_hash_expected, tls->ech.inner_client_random, label, message)) != 0) goto Exit; tls->ech.accepted = ptls_mem_equal(confirm_hash_delivered, confirm_hash_expected, sizeof(confirm_hash_delivered)); memcpy(message.base + confirm_hash_off, confirm_hash_delivered, sizeof(confirm_hash_delivered)); if (tls->ech.accepted) goto Exit; } /* dispose ECH AEAD state to indicate rejection, adopting outer CH for the rest of the handshake */ ptls_aead_free(tls->ech.aead); tls->ech.aead = NULL; key_schedule_select_outer(tls->key_schedule); Exit: PTLS_PROBE(ECH_SELECTION, tls, !!tls->ech.accepted); PTLS_LOG_CONN(ech_selection, tls, { PTLS_LOG_ELEMENT_BOOL(is_ech, tls->ech.accepted); }); ptls_clear_memory(confirm_hash_expected, sizeof(confirm_hash_expected)); return ret; } static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, ptls_handshake_properties_t *properties) { struct st_ptls_server_hello_t sh; ptls_iovec_t ecdh_secret = {NULL}; int ret; if ((ret = decode_server_hello(tls, &sh, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len)) != 0) goto Exit; if (!(sh.legacy_session_id.len == tls->client.legacy_session_id.len && ptls_mem_equal(sh.legacy_session_id.base, tls->client.legacy_session_id.base, tls->client.legacy_session_id.len))) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } if (sh.is_retry_request) { if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, 0)) != 0) goto Exit; key_schedule_transform_post_ch1hash(tls->key_schedule); if (tls->ech.aead != NULL) { size_t confirm_hash_off = 0; if (tls->ech.offered) { if (sh.retry_request.ech != NULL) confirm_hash_off = sh.retry_request.ech - message.base; } else { assert(tls->ech.offered_grease); } if ((ret = client_ech_select_hello(tls, message, confirm_hash_off, ECH_CONFIRMATION_HRR)) != 0) goto Exit; } ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); return handle_hello_retry_request(tls, emitter, &sh, message, properties); } if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, tls->client.offered_psk && !tls->is_psk_handshake)) != 0) goto Exit; /* check if ECH is accepted */ if (tls->ech.aead != NULL) { size_t confirm_hash_off = 0; if (tls->ech.offered) { confirm_hash_off = PTLS_HANDSHAKE_HEADER_SIZE + 2 /* legacy_version */ + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH; } else { assert(tls->ech.offered_grease); } if ((ret = client_ech_select_hello(tls, message, confirm_hash_off, ECH_CONFIRMATION_SERVER_HELLO)) != 0) goto Exit; } /* clear sensitive and space-consuming ECH state, now that are done with handling sending and decoding Hellos */ clear_ech(&tls->ech, 0); if (tls->key_schedule->hashes[0].ctx_outer != NULL) { tls->key_schedule->hashes[0].ctx_outer->final(tls->key_schedule->hashes[0].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE); tls->key_schedule->hashes[0].ctx_outer = NULL; } ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); if (sh.peerkey.base != NULL) { if ((ret = tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, 1, &ecdh_secret, sh.peerkey)) != 0) goto Exit; } if ((ret = key_schedule_extract(tls->key_schedule, ecdh_secret)) != 0) goto Exit; if ((ret = setup_traffic_protection(tls, 0, "s hs traffic", 2, 0)) != 0) goto Exit; if (tls->client.using_early_data) { if ((tls->pending_handshake_secret = malloc(PTLS_MAX_DIGEST_SIZE)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } if ((ret = derive_secret(tls->key_schedule, tls->pending_handshake_secret, "c hs traffic")) != 0) goto Exit; if (tls->ctx->update_traffic_key != NULL && (ret = tls->ctx->update_traffic_key->cb(tls->ctx->update_traffic_key, tls, 1, 2, tls->pending_handshake_secret)) != 0) goto Exit; } else { if ((ret = setup_traffic_protection(tls, 1, "c hs traffic", 2, 0)) != 0) goto Exit; } tls->state = PTLS_STATE_CLIENT_EXPECT_ENCRYPTED_EXTENSIONS; ret = PTLS_ERROR_IN_PROGRESS; Exit: if (ecdh_secret.base != NULL) { ptls_clear_memory(ecdh_secret.base, ecdh_secret.len); free(ecdh_secret.base); } return ret; } static int should_collect_unknown_extension(ptls_t *tls, ptls_handshake_properties_t *properties, uint16_t type) { return properties != NULL && properties->collect_extension != NULL && properties->collect_extension(tls, properties, type); } static int collect_unknown_extension(ptls_t *tls, uint16_t type, const uint8_t *src, const uint8_t *const end, ptls_raw_extension_t *slots) { size_t i; for (i = 0; slots[i].type != UINT16_MAX; ++i) { assert(i < MAX_UNKNOWN_EXTENSIONS); if (slots[i].type == type) return PTLS_ALERT_ILLEGAL_PARAMETER; } if (i < MAX_UNKNOWN_EXTENSIONS) { slots[i].type = type; slots[i].data = ptls_iovec_init(src, end - src); slots[i + 1].type = UINT16_MAX; } return 0; } static int report_unknown_extensions(ptls_t *tls, ptls_handshake_properties_t *properties, ptls_raw_extension_t *slots) { if (properties != NULL && properties->collect_extension != NULL) { assert(properties->collected_extensions != NULL); return properties->collected_extensions(tls, properties, slots); } else { return 0; } } static int client_handle_encrypted_extensions(ptls_t *tls, ptls_iovec_t message, ptls_handshake_properties_t *properties) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; uint16_t type; static const ptls_raw_extension_t no_unknown_extensions = {UINT16_MAX}; ptls_raw_extension_t *unknown_extensions = (ptls_raw_extension_t *)&no_unknown_extensions; int ret, skip_early_data = 1; uint8_t server_offered_cert_type = PTLS_CERTIFICATE_TYPE_X509; decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, &type, { if (tls->ctx->on_extension != NULL && (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, type, ptls_iovec_init(src, end - src)) != 0)) goto Exit; switch (type) { case PTLS_EXTENSION_TYPE_SERVER_NAME: if (src != end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if (!(tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name))) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } break; case PTLS_EXTENSION_TYPE_ALPN: ptls_decode_block(src, end, 2, { ptls_decode_open_block(src, end, 1, { if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if ((ret = ptls_set_negotiated_protocol(tls, (const char *)src, end - src)) != 0) goto Exit; src = end; }); if (src != end) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } }); break; case PTLS_EXTENSION_TYPE_EARLY_DATA: if (!tls->client.using_early_data) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } skip_early_data = 0; break; case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE: if (end - src != 1) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } server_offered_cert_type = *src; src = end; break; case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO: { /* accept retry_configs only if we offered ECH but rejected */ if (!((tls->ech.offered || tls->ech.offered_grease) && !ptls_is_ech_handshake(tls, NULL, NULL, NULL))) { ret = PTLS_ALERT_UNSUPPORTED_EXTENSION; goto Exit; } /* parse retry_config, and if it is applicable, provide that to the application */ struct st_decoded_ech_config_t decoded; if ((ret = client_decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0) goto Exit; if (decoded.kem != NULL && decoded.cipher != NULL && properties != NULL && properties->client.ech.retry_configs != NULL) { if ((properties->client.ech.retry_configs->base = malloc(end - src)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } memcpy(properties->client.ech.retry_configs->base, src, end - src); properties->client.ech.retry_configs->len = end - src; } src = end; } break; default: if (should_collect_unknown_extension(tls, properties, type)) { if (unknown_extensions == &no_unknown_extensions) { if ((unknown_extensions = malloc(sizeof(*unknown_extensions) * (MAX_UNKNOWN_EXTENSIONS + 1))) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } unknown_extensions[0].type = UINT16_MAX; } if ((ret = collect_unknown_extension(tls, type, src, end, unknown_extensions)) != 0) goto Exit; } break; } src = end; }); if (server_offered_cert_type != (tls->ctx->use_raw_public_keys ? PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY : PTLS_CERTIFICATE_TYPE_X509)) { ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; goto Exit; } if (tls->client.using_early_data) { if (skip_early_data) tls->client.using_early_data = 0; if (properties != NULL) properties->client.early_data_acceptance = skip_early_data ? PTLS_EARLY_DATA_REJECTED : PTLS_EARLY_DATA_ACCEPTED; } if ((ret = report_unknown_extensions(tls, properties, unknown_extensions)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); tls->state = tls->is_psk_handshake ? PTLS_STATE_CLIENT_EXPECT_FINISHED : PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE; ret = PTLS_ERROR_IN_PROGRESS; Exit: if (unknown_extensions != &no_unknown_extensions) free(unknown_extensions); return ret; } static int decode_certificate_request(ptls_t *tls, struct st_ptls_certificate_request_t *cr, const uint8_t *src, const uint8_t *const end) { int ret; uint16_t exttype = 0; /* certificate request context */ ptls_decode_open_block(src, end, 1, { size_t len = end - src; if (len > 255) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if ((cr->context.base = malloc(len != 0 ? len : 1)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } cr->context.len = len; memcpy(cr->context.base, src, len); src = end; }); /* decode extensions */ decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST, &exttype, { if (tls->ctx->on_extension != NULL && (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST, exttype, ptls_iovec_init(src, end - src)) != 0)) goto Exit; switch (exttype) { case PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS: if ((ret = decode_signature_algorithms(&cr->signature_algorithms, &src, end)) != 0) goto Exit; break; } src = end; }); if (cr->signature_algorithms.count == 0) { ret = PTLS_ALERT_MISSING_EXTENSION; goto Exit; } ret = 0; Exit: return ret; } int ptls_build_certificate_message(ptls_buffer_t *buf, ptls_iovec_t context, ptls_iovec_t *certificates, size_t num_certificates, ptls_iovec_t ocsp_status) { int ret; ptls_buffer_push_block(buf, 1, { ptls_buffer_pushv(buf, context.base, context.len); }); ptls_buffer_push_block(buf, 3, { size_t i; for (i = 0; i != num_certificates; ++i) { ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, certificates[i].base, certificates[i].len); }); ptls_buffer_push_block(buf, 2, { if (i == 0 && ocsp_status.len != 0) { buffer_push_extension(buf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, { ptls_buffer_push(buf, 1); /* status_type == ocsp */ ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, ocsp_status.base, ocsp_status.len); }); }); } }); } }); ret = 0; Exit: return ret; } static int default_emit_certificate_cb(ptls_emit_certificate_t *_self, ptls_t *tls, ptls_message_emitter_t *emitter, ptls_key_schedule_t *key_sched, ptls_iovec_t context, int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos) { int ret; ptls_push_message(emitter, key_sched, PTLS_HANDSHAKE_TYPE_CERTIFICATE, { if ((ret = ptls_build_certificate_message(emitter->buf, context, tls->ctx->certificates.list, tls->ctx->certificates.count, ptls_iovec_init(NULL, 0))) != 0) goto Exit; }); ret = 0; Exit: return ret; } static int send_certificate(ptls_t *tls, ptls_message_emitter_t *emitter, struct st_ptls_signature_algorithms_t *signature_algorithms, ptls_iovec_t context, int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos) { int ret; if (signature_algorithms->count == 0) { ret = PTLS_ALERT_MISSING_EXTENSION; goto Exit; } { /* send Certificate (or the equivalent) */ static ptls_emit_certificate_t default_emit_certificate = {default_emit_certificate_cb}; ptls_emit_certificate_t *emit_certificate = tls->ctx->emit_certificate != NULL ? tls->ctx->emit_certificate : &default_emit_certificate; Redo: if ((ret = emit_certificate->cb(emit_certificate, tls, emitter, tls->key_schedule, context, push_status_request, compress_algos, num_compress_algos)) != 0) { if (ret == PTLS_ERROR_DELEGATE) { assert(emit_certificate != &default_emit_certificate); emit_certificate = &default_emit_certificate; goto Redo; } goto Exit; } } Exit: return ret; } static int send_certificate_verify(ptls_t *tls, ptls_message_emitter_t *emitter, struct st_ptls_signature_algorithms_t *signature_algorithms, const char *context_string) { size_t start_off = emitter->buf->off; int ret; if (tls->ctx->sign_certificate == NULL) return 0; /* build and send CertificateVerify */ ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, { ptls_buffer_t *sendbuf = emitter->buf; size_t algo_off = sendbuf->off; ptls_buffer_push16(sendbuf, 0); /* filled in later */ ptls_buffer_push_block(sendbuf, 2, { uint16_t algo; uint8_t data[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE]; size_t datalen = build_certificate_verify_signdata(data, tls->key_schedule, context_string); if ((ret = tls->ctx->sign_certificate->cb( tls->ctx->sign_certificate, tls, tls->is_server ? &tls->server.async_job : NULL, &algo, sendbuf, ptls_iovec_init(data, datalen), signature_algorithms != NULL ? signature_algorithms->list : NULL, signature_algorithms != NULL ? signature_algorithms->count : 0)) != 0) { if (ret == PTLS_ERROR_ASYNC_OPERATION) { assert(tls->is_server || !"async operation only supported on the server-side"); assert(tls->server.async_job != NULL); /* Reset the output to the end of the previous handshake message. CertificateVerify will be rebuilt when the * async operation completes. */ emitter->buf->off = start_off; } else { assert(tls->server.async_job == NULL); } goto Exit; } assert(tls->server.async_job == NULL); sendbuf->base[algo_off] = (uint8_t)(algo >> 8); sendbuf->base[algo_off + 1] = (uint8_t)algo; }); }); Exit: return ret; } static int client_handle_certificate_request(ptls_t *tls, ptls_iovec_t message, ptls_handshake_properties_t *properties) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; int ret = 0; assert(!tls->is_psk_handshake && "state machine asserts that this message is never delivered when PSK is used"); if ((ret = decode_certificate_request(tls, &tls->client.certificate_request, src, end)) != 0) return ret; /* This field SHALL be zero length unless used for the post-handshake authentication exchanges (section 4.3.2) */ if (tls->client.certificate_request.context.len != 0) return PTLS_ALERT_ILLEGAL_PARAMETER; tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); return PTLS_ERROR_IN_PROGRESS; } static int handle_certificate(ptls_t *tls, const uint8_t *src, const uint8_t *end, int *got_certs) { ptls_iovec_t certs[16]; size_t num_certs = 0; int ret = 0; /* certificate request context */ ptls_decode_open_block(src, end, 1, { if (src != end) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } }); /* certificate_list */ ptls_decode_block(src, end, 3, { while (src != end) { ptls_decode_open_block(src, end, 3, { if (num_certs < PTLS_ELEMENTSOF(certs)) certs[num_certs++] = ptls_iovec_init(src, end - src); src = end; }); uint16_t type; decode_open_extensions(src, end, PTLS_HANDSHAKE_TYPE_CERTIFICATE, &type, { if (tls->ctx->on_extension != NULL && (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_CERTIFICATE, type, ptls_iovec_init(src, end - src)) != 0)) goto Exit; src = end; }); } }); if (tls->ctx->verify_certificate != NULL) { const char *server_name = NULL; if (!ptls_is_server(tls)) { if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL)) { server_name = tls->ech.client.public_name; } else { server_name = tls->server_name; } } if ((ret = tls->ctx->verify_certificate->cb(tls->ctx->verify_certificate, tls, server_name, &tls->certificate_verify.cb, &tls->certificate_verify.verify_ctx, certs, num_certs)) != 0) goto Exit; } *got_certs = num_certs != 0; Exit: return ret; } static int client_do_handle_certificate(ptls_t *tls, const uint8_t *src, const uint8_t *end) { int got_certs, ret; if ((ret = handle_certificate(tls, src, end, &got_certs)) != 0) return ret; if (!got_certs) return PTLS_ALERT_ILLEGAL_PARAMETER; return 0; } static int client_handle_certificate(ptls_t *tls, ptls_iovec_t message) { int ret; if ((ret = client_do_handle_certificate(tls, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len)) != 0) return ret; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY; return PTLS_ERROR_IN_PROGRESS; } static int client_handle_compressed_certificate(ptls_t *tls, ptls_iovec_t message) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; uint16_t algo; uint32_t uncompressed_size; uint8_t *uncompressed = NULL; int ret; if (tls->ctx->decompress_certificate == NULL) { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; goto Exit; } /* decode */ if ((ret = ptls_decode16(&algo, &src, end)) != 0) goto Exit; if ((ret = ptls_decode24(&uncompressed_size, &src, end)) != 0) goto Exit; if (uncompressed_size > 65536) { /* TODO find a sensible number */ ret = PTLS_ALERT_BAD_CERTIFICATE; goto Exit; } if ((uncompressed = malloc(uncompressed_size)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } ptls_decode_block(src, end, 3, { if ((ret = tls->ctx->decompress_certificate->cb(tls->ctx->decompress_certificate, tls, algo, ptls_iovec_init(uncompressed, uncompressed_size), ptls_iovec_init(src, end - src))) != 0) goto Exit; src = end; }); /* handle */ if ((ret = client_do_handle_certificate(tls, uncompressed, uncompressed + uncompressed_size)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY; ret = PTLS_ERROR_IN_PROGRESS; Exit: free(uncompressed); return ret; } static int server_handle_certificate(ptls_t *tls, ptls_iovec_t message) { int got_certs, ret; if ((ret = handle_certificate(tls, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, &got_certs)) != 0) return ret; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); if (got_certs) { tls->state = PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY; } else { /* Client did not provide certificate, and the verifier says we can fail open. Therefore, the next message is Finished. */ tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED; } return PTLS_ERROR_IN_PROGRESS; } static int handle_certificate_verify(ptls_t *tls, ptls_iovec_t message, const char *context_string) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; uint16_t algo; ptls_iovec_t signature; uint8_t signdata[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE]; size_t signdata_size; int ret; /* decode */ if ((ret = ptls_decode16(&algo, &src, end)) != 0) goto Exit; ptls_decode_block(src, end, 2, { signature = ptls_iovec_init(src, end - src); src = end; }); signdata_size = build_certificate_verify_signdata(signdata, tls->key_schedule, context_string); if (tls->certificate_verify.cb != NULL) { ret = tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, algo, ptls_iovec_init(signdata, signdata_size), signature); } else { ret = 0; } ptls_clear_memory(signdata, signdata_size); tls->certificate_verify.cb = NULL; if (ret != 0) { goto Exit; } ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); Exit: return ret; } static int client_handle_certificate_verify(ptls_t *tls, ptls_iovec_t message) { int ret = handle_certificate_verify(tls, message, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING); if (ret == 0) { tls->state = PTLS_STATE_CLIENT_EXPECT_FINISHED; ret = PTLS_ERROR_IN_PROGRESS; } return ret; } static int server_handle_certificate_verify(ptls_t *tls, ptls_iovec_t message) { int ret = handle_certificate_verify(tls, message, PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING); if (ret == 0) { tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED; ret = PTLS_ERROR_IN_PROGRESS; } return ret; } static int client_handle_finished(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message) { uint8_t send_secret[PTLS_MAX_DIGEST_SIZE]; int alert_ech_required = tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL), ret; if ((ret = verify_finished(tls, message)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); /* update traffic keys by using messages upto ServerFinished, but commission them after sending ClientFinished */ if ((ret = key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0))) != 0) goto Exit; if ((ret = setup_traffic_protection(tls, 0, "s ap traffic", 3, 0)) != 0) goto Exit; if ((ret = derive_secret(tls->key_schedule, send_secret, "c ap traffic")) != 0) goto Exit; if ((ret = derive_exporter_secret(tls, 0)) != 0) goto Exit; /* if sending early data, emit EOED and commision the client handshake traffic secret */ if (tls->pending_handshake_secret != NULL) { assert(tls->traffic_protection.enc.aead != NULL || tls->ctx->update_traffic_key != NULL); if (tls->client.using_early_data && !tls->ctx->omit_end_of_early_data) ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA, {}); tls->client.using_early_data = 0; if ((ret = commission_handshake_secret(tls)) != 0) goto Exit; } if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; if (!alert_ech_required && tls->client.certificate_request.context.base != NULL) { if ((ret = send_certificate(tls, emitter, &tls->client.certificate_request.signature_algorithms, tls->client.certificate_request.context, 0, NULL, 0)) == 0) ret = send_certificate_verify(tls, emitter, &tls->client.certificate_request.signature_algorithms, PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING); free(tls->client.certificate_request.context.base); tls->client.certificate_request.context = ptls_iovec_init(NULL, 0); if (ret != 0) goto Exit; } ret = send_finished(tls, emitter); memcpy(tls->traffic_protection.enc.secret, send_secret, sizeof(send_secret)); if ((ret = setup_traffic_protection(tls, 1, NULL, 3, 0)) != 0) goto Exit; tls->state = PTLS_STATE_CLIENT_POST_HANDSHAKE; /* if ECH was rejected, close the connection with ECH_REQUIRED alert after verifying messages up to Finished */ if (alert_ech_required) ret = PTLS_ALERT_ECH_REQUIRED; Exit: ptls_clear_memory(send_secret, sizeof(send_secret)); return ret; } static int client_handle_new_session_ticket(ptls_t *tls, ptls_iovec_t message) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; ptls_iovec_t ticket_nonce; int ret; { /* verify the format */ uint32_t ticket_lifetime, ticket_age_add, max_early_data_size; ptls_iovec_t ticket; if ((ret = decode_new_session_ticket(tls, &ticket_lifetime, &ticket_age_add, &ticket_nonce, &ticket, &max_early_data_size, src, end)) != 0) return ret; } /* do nothing if use of session ticket is disabled */ if (tls->ctx->save_ticket == NULL) return 0; /* save the extension, along with the key of myself */ ptls_buffer_t ticket_buf; ptls_buffer_init(&ticket_buf, "", 0); ptls_buffer_push64(&ticket_buf, tls->ctx->get_time->cb(tls->ctx->get_time)); ptls_buffer_push16(&ticket_buf, tls->key_share->id); ptls_buffer_push16(&ticket_buf, tls->cipher_suite->id); ptls_buffer_push_block(&ticket_buf, 3, { ptls_buffer_pushv(&ticket_buf, src, end - src); }); ptls_buffer_push_block(&ticket_buf, 2, { if ((ret = ptls_buffer_reserve(&ticket_buf, tls->key_schedule->hashes[0].algo->digest_size)) != 0) goto Exit; if ((ret = derive_resumption_secret(tls->key_schedule, ticket_buf.base + ticket_buf.off, ticket_nonce)) != 0) goto Exit; ticket_buf.off += tls->key_schedule->hashes[0].algo->digest_size; }); if ((ret = tls->ctx->save_ticket->cb(tls->ctx->save_ticket, tls, ptls_iovec_init(ticket_buf.base, ticket_buf.off))) != 0) goto Exit; ret = 0; Exit: ptls_buffer_dispose(&ticket_buf); return ret; } static int client_hello_decode_server_name(ptls_iovec_t *name, const uint8_t **src, const uint8_t *const end) { int ret = 0; ptls_decode_open_block(*src, end, 2, { do { uint8_t type; if ((ret = ptls_decode8(&type, src, end)) != 0) goto Exit; ptls_decode_open_block(*src, end, 2, { switch (type) { case PTLS_SERVER_NAME_TYPE_HOSTNAME: if (memchr(*src, '\0', end - *src) != 0) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } *name = ptls_iovec_init(*src, end - *src); break; default: break; } *src = end; }); } while (*src != end); }); Exit: return ret; } static int select_negotiated_group(ptls_key_exchange_algorithm_t **selected, ptls_key_exchange_algorithm_t **candidates, const uint8_t *src, const uint8_t *const end) { int ret; ptls_decode_block(src, end, 2, { while (src != end) { uint16_t group; if ((ret = ptls_decode16(&group, &src, end)) != 0) goto Exit; ptls_key_exchange_algorithm_t **c = candidates; for (; *c != NULL; ++c) { if ((*c)->id == group) { *selected = *c; return 0; } } } }); ret = PTLS_ALERT_HANDSHAKE_FAILURE; Exit: return ret; } static int decode_client_hello(ptls_context_t *ctx, struct st_ptls_client_hello_t *ch, const uint8_t *src, const uint8_t *const end, ptls_handshake_properties_t *properties, ptls_t *tls_cbarg) { const uint8_t *start = src; uint16_t exttype = 0; int ret; /* decode protocol version (do not bare to decode something older than TLS 1.0) */ if ((ret = ptls_decode16(&ch->legacy_version, &src, end)) != 0) goto Exit; if (ch->legacy_version < 0x0301) { ret = PTLS_ALERT_PROTOCOL_VERSION; goto Exit; } /* skip random */ if (end - src < PTLS_HELLO_RANDOM_SIZE) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->random_bytes = src; src += PTLS_HELLO_RANDOM_SIZE; /* skip legacy_session_id */ ptls_decode_open_block(src, end, 1, { if (end - src > 32) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->legacy_session_id = ptls_iovec_init(src, end - src); src = end; }); /* decode and select from ciphersuites */ ptls_decode_open_block(src, end, 2, { if ((end - src) % 2 != 0) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->cipher_suites = ptls_iovec_init(src, end - src); src = end; }); /* decode legacy_compression_methods */ ptls_decode_open_block(src, end, 1, { if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->compression_methods.ids = src; ch->compression_methods.count = end - src; src = end; }); /* CH defined in TLS versions below 1.2 might not have extensions (or they might, see what OpenSSL 1.0.0 sends); so bail out * after parsing the main variables. Zero is returned as it is a valid ClientHello. However `ptls_t::selected_version` remains * zero indicating that no compatible version were found. */ if (ch->legacy_version < 0x0303 && src == end) { ret = 0; goto Exit; } /* decode extensions */ ch->first_extension_at = src - start + 2; decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, &exttype, { ch->psk.is_last_extension = 0; if (ctx->on_extension != NULL && tls_cbarg != NULL && (ret = ctx->on_extension->cb(ctx->on_extension, tls_cbarg, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, exttype, ptls_iovec_init(src, end - src)) != 0)) goto Exit; switch (exttype) { case PTLS_EXTENSION_TYPE_SERVER_NAME: if ((ret = client_hello_decode_server_name(&ch->server_name, &src, end)) != 0) goto Exit; if (src != end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } break; case PTLS_EXTENSION_TYPE_ALPN: ptls_decode_block(src, end, 2, { do { ptls_decode_open_block(src, end, 1, { /* rfc7301 3.1: empty strings MUST NOT be included */ if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if (ch->alpn.count < PTLS_ELEMENTSOF(ch->alpn.list)) ch->alpn.list[ch->alpn.count++] = ptls_iovec_init(src, end - src); src = end; }); } while (src != end); }); break; case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE: ptls_decode_block(src, end, 1, { size_t list_size = end - src; /* RFC7250 4.1: No empty list, no list with single x509 element */ if (list_size == 0 || (list_size == 1 && *src == PTLS_CERTIFICATE_TYPE_X509)) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } do { if (ch->server_certificate_types.count < PTLS_ELEMENTSOF(ch->server_certificate_types.list)) ch->server_certificate_types.list[ch->server_certificate_types.count++] = *src; src++; } while (src != end); }); break; case PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE: ptls_decode_block(src, end, 1, { do { uint16_t id; if ((ret = ptls_decode16(&id, &src, end)) != 0) goto Exit; if (ch->cert_compression_algos.count < PTLS_ELEMENTSOF(ch->cert_compression_algos.list)) ch->cert_compression_algos.list[ch->cert_compression_algos.count++] = id; } while (src != end); }); break; case PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS: ch->negotiated_groups = ptls_iovec_init(src, end - src); break; case PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS: if ((ret = decode_signature_algorithms(&ch->signature_algorithms, &src, end)) != 0) goto Exit; break; case PTLS_EXTENSION_TYPE_KEY_SHARE: ch->key_shares = ptls_iovec_init(src, end - src); break; case PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS: ptls_decode_block(src, end, 1, { size_t selected_index = PTLS_ELEMENTSOF(supported_versions); do { size_t i; uint16_t v; if ((ret = ptls_decode16(&v, &src, end)) != 0) goto Exit; for (i = 0; i != selected_index; ++i) { if (supported_versions[i] == v) { selected_index = i; break; } } } while (src != end); if (selected_index != PTLS_ELEMENTSOF(supported_versions)) ch->selected_version = supported_versions[selected_index]; }); break; case PTLS_EXTENSION_TYPE_COOKIE: if (properties == NULL || properties->server.cookie.key == NULL) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } ch->cookie.all = ptls_iovec_init(src, end - src); ptls_decode_block(src, end, 2, { ch->cookie.tbs.base = (void *)src; ptls_decode_open_block(src, end, 2, { ptls_decode_open_block(src, end, 1, { ch->cookie.ch1_hash = ptls_iovec_init(src, end - src); src = end; }); uint8_t sent_key_share; if ((ret = ptls_decode8(&sent_key_share, &src, end)) != 0) goto Exit; switch (sent_key_share) { case 0: assert(!ch->cookie.sent_key_share); break; case 1: ch->cookie.sent_key_share = 1; break; default: ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } }); ch->cookie.tbs.len = src - ch->cookie.tbs.base; ptls_decode_block(src, end, 1, { ch->cookie.signature = ptls_iovec_init(src, end - src); src = end; }); }); break; case PTLS_EXTENSION_TYPE_PRE_SHARED_KEY: { size_t num_identities = 0; ptls_decode_open_block(src, end, 2, { do { struct st_ptls_client_hello_psk_t psk = {{NULL}}; ptls_decode_open_block(src, end, 2, { psk.identity = ptls_iovec_init(src, end - src); src = end; }); if ((ret = ptls_decode32(&psk.obfuscated_ticket_age, &src, end)) != 0) goto Exit; if (ch->psk.identities.count < PTLS_ELEMENTSOF(ch->psk.identities.list)) ch->psk.identities.list[ch->psk.identities.count++] = psk; ++num_identities; } while (src != end); }); ch->psk.hash_end = src; ptls_decode_block(src, end, 2, { size_t num_binders = 0; do { ptls_decode_open_block(src, end, 1, { if (num_binders < ch->psk.identities.count) ch->psk.identities.list[num_binders].binder = ptls_iovec_init(src, end - src); src = end; }); ++num_binders; } while (src != end); if (num_identities != num_binders) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } }); ch->psk.is_last_extension = 1; } break; case PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES: ptls_decode_block(src, end, 1, { do { uint8_t mode; if ((ret = ptls_decode8(&mode, &src, end)) != 0) goto Exit; if (mode < sizeof(ch->psk.ke_modes) * 8) ch->psk.ke_modes |= 1u << mode; } while (src != end); }); break; case PTLS_EXTENSION_TYPE_EARLY_DATA: ch->psk.early_data_indication = 1; break; case PTLS_EXTENSION_TYPE_STATUS_REQUEST: ch->status_request = 1; break; case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO: if ((ret = ptls_decode8(&ch->ech.type, &src, end)) != 0) goto Exit; switch (ch->ech.type) { case PTLS_ECH_CLIENT_HELLO_TYPE_OUTER: if ((ret = ptls_decode16(&ch->ech.cipher_suite.kdf, &src, end)) != 0 || (ret = ptls_decode16(&ch->ech.cipher_suite.aead, &src, end)) != 0) goto Exit; if ((ret = ptls_decode8(&ch->ech.config_id, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 2, { ch->ech.enc = ptls_iovec_init(src, end - src); src = end; }); ptls_decode_open_block(src, end, 2, { if (src == end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->ech.payload = ptls_iovec_init(src, end - src); src = end; }); break; case PTLS_ECH_CLIENT_HELLO_TYPE_INNER: if (src != end) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ch->ech.payload = ptls_iovec_init("", 0); /* non-zero base indicates that the extension was received */ break; default: ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } src = end; break; default: if (tls_cbarg != NULL && should_collect_unknown_extension(tls_cbarg, properties, exttype)) { if ((ret = collect_unknown_extension(tls_cbarg, exttype, src, end, ch->unknown_extensions)) != 0) goto Exit; } break; } src = end; }); ret = 0; Exit: return ret; } static int rebuild_ch_inner_extensions(ptls_buffer_t *buf, const uint8_t **src, const uint8_t *const end, const uint8_t *outer_ext, const uint8_t *outer_ext_end) { int ret; ptls_buffer_push_block(buf, 2, { ptls_decode_open_block(*src, end, 2, { while (*src != end) { uint16_t exttype; if ((ret = ptls_decode16(&exttype, src, end)) != 0) goto Exit; ptls_decode_open_block(*src, end, 2, { if (exttype == PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS) { ptls_decode_open_block(*src, end, 1, { do { uint16_t reftype; uint16_t outertype; uint16_t outersize; if ((ret = ptls_decode16(&reftype, src, end)) != 0) goto Exit; if (reftype == PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } while (1) { if (ptls_decode16(&outertype, &outer_ext, outer_ext_end) != 0 || ptls_decode16(&outersize, &outer_ext, outer_ext_end) != 0) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } assert(outer_ext_end - outer_ext >= outersize); if (outertype == reftype) break; outer_ext += outersize; } buffer_push_extension(buf, reftype, { ptls_buffer_pushv(buf, outer_ext, outersize); outer_ext += outersize; }); } while (*src != end); }); } else { buffer_push_extension(buf, exttype, { ptls_buffer_pushv(buf, *src, end - *src); *src = end; }); } }); } }); }); Exit: return ret; } static int rebuild_ch_inner(ptls_buffer_t *buf, const uint8_t *src, const uint8_t *const end, struct st_ptls_client_hello_t *outer_ch, const uint8_t *outer_ext, const uint8_t *outer_ext_end) { #define COPY_BLOCK(capacity) \ do { \ ptls_decode_open_block(src, end, (capacity), { \ ptls_buffer_push_block(buf, (capacity), { ptls_buffer_pushv(buf, src, end - src); }); \ src = end; \ }); \ } while (0) int ret; ptls_buffer_push_message_body(buf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, { { /* legacy_version */ uint16_t legacy_version; if ((ret = ptls_decode16(&legacy_version, &src, end)) != 0) goto Exit; ptls_buffer_push16(buf, legacy_version); } /* hello random */ if (end - src < PTLS_HELLO_RANDOM_SIZE) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } ptls_buffer_pushv(buf, src, PTLS_HELLO_RANDOM_SIZE); src += PTLS_HELLO_RANDOM_SIZE; ptls_decode_open_block(src, end, 1, { if (src != end) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } }); ptls_buffer_push_block(buf, 1, { ptls_buffer_pushv(buf, outer_ch->legacy_session_id.base, outer_ch->legacy_session_id.len); }); /* cipher-suites and legacy-compression-methods */ COPY_BLOCK(2); COPY_BLOCK(1); /* extensions */ if ((ret = rebuild_ch_inner_extensions(buf, &src, end, outer_ext, outer_ext_end)) != 0) goto Exit; }); /* padding must be all zero */ for (; src != end; ++src) { if (*src != '\0') { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } Exit: return ret; #undef COPY_BLOCK } /* Wrapper function for invoking the on_client_hello callback, taking an exhaustive list of parameters as arguments. The intention * is to not miss setting them as we add new parameters to the struct. */ static inline int call_on_client_hello_cb(ptls_t *tls, ptls_iovec_t server_name, ptls_iovec_t raw_message, ptls_iovec_t cipher_suites, ptls_iovec_t *alpns, size_t num_alpns, const uint16_t *sig_algos, size_t num_sig_algos, const uint16_t *cert_comp_algos, size_t num_cert_comp_algos, const uint8_t *server_cert_types, size_t num_server_cert_types, int incompatible_version) { if (tls->ctx->on_client_hello == NULL) return 0; ptls_on_client_hello_parameters_t params = {server_name, raw_message, cipher_suites, {alpns, num_alpns}, {sig_algos, num_sig_algos}, {cert_comp_algos, num_cert_comp_algos}, {server_cert_types, num_server_cert_types}, incompatible_version}; return tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ¶ms); } static int check_client_hello_constraints(ptls_context_t *ctx, struct st_ptls_client_hello_t *ch, const void *prev_random, int ech_is_inner_ch, ptls_iovec_t raw_message, ptls_t *tls_cbarg) { int is_second_flight = prev_random != 0; /* The following check is necessary so that we would be able to track the connection in SSLKEYLOGFILE, even though it might not * be for the safety of the protocol. */ if (is_second_flight && !ptls_mem_equal(ch->random_bytes, prev_random, PTLS_HELLO_RANDOM_SIZE)) return PTLS_ALERT_HANDSHAKE_FAILURE; /* bail out if CH cannot be handled as TLS 1.3 */ if (!is_supported_version(ch->selected_version)) { /* ECH: server MUST abort with an "illegal_parameter" alert if the client offers TLS 1.2 or below (draft-15 7.1) */ if (ech_is_inner_ch) return PTLS_ALERT_ILLEGAL_PARAMETER; /* fail with PROTOCOL_VERSION alert, after providing the applications the raw CH and SNI to help them fallback */ if (!is_second_flight) { int ret; if ((ret = call_on_client_hello_cb(tls_cbarg, ch->server_name, raw_message, ch->cipher_suites, ch->alpn.list, ch->alpn.count, NULL, 0, NULL, 0, NULL, 0, 1)) != 0) return ret; } return PTLS_ALERT_PROTOCOL_VERSION; } /* Check TLS 1.3-specific constraints. Hereafter, we might exit without calling on_client_hello. That's fine because this CH is * ought to be rejected. */ if (ch->legacy_version <= 0x0300) { /* RFC 8446 Appendix D.5: any endpoint receiving a Hello message with legacy_version set to 0x0300 MUST abort the handshake * with a "protocol_version" alert. */ return PTLS_ALERT_PROTOCOL_VERSION; } if (!(ch->compression_methods.count == 1 && ch->compression_methods.ids[0] == 0)) return PTLS_ALERT_ILLEGAL_PARAMETER; /* pre-shared key */ if (ch->psk.hash_end != NULL) { /* PSK must be the last extension */ if (!ch->psk.is_last_extension) return PTLS_ALERT_ILLEGAL_PARAMETER; } else { if (ch->psk.early_data_indication) return PTLS_ALERT_ILLEGAL_PARAMETER; } if (ech_is_inner_ch && ch->ech.payload.base == NULL) return PTLS_ALERT_ILLEGAL_PARAMETER; if (ch->ech.payload.base != NULL && ch->ech.type != (ech_is_inner_ch ? PTLS_ECH_CLIENT_HELLO_TYPE_INNER : PTLS_ECH_CLIENT_HELLO_TYPE_OUTER)) return PTLS_ALERT_ILLEGAL_PARAMETER; return 0; } static int vec_is_string(ptls_iovec_t x, const char *y) { return strncmp((const char *)x.base, y, x.len) == 0 && y[x.len] == '\0'; } static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, ptls_iovec_t ch_trunc) { ptls_buffer_t decbuf; ptls_iovec_t ticket_psk, ticket_server_name, ticket_negotiated_protocol; uint64_t issue_at, now = tls->ctx->get_time->cb(tls->ctx->get_time); uint32_t age_add; uint16_t ticket_key_exchange_id, ticket_csid; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; int ret; ptls_buffer_init(&decbuf, "", 0); for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index; /* decrypt and decode */ int can_accept_early_data = 1; decbuf.off = 0; switch (tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) { case 0: /* decrypted */ break; case PTLS_ERROR_REJECT_EARLY_DATA: /* decrypted, but early data is rejected */ can_accept_early_data = 0; break; default: /* decryption failure */ continue; } if (decode_session_identifier(&issue_at, &ticket_psk, &age_add, &ticket_server_name, &ticket_key_exchange_id, &ticket_csid, &ticket_negotiated_protocol, decbuf.base, decbuf.base + decbuf.off) != 0) continue; /* check age */ if (now < issue_at) continue; if (now - issue_at > (uint64_t)tls->ctx->ticket_lifetime * 1000) continue; *accept_early_data = 0; if (ch->psk.early_data_indication && can_accept_early_data) { /* accept early-data if abs(diff) between the reported age and the actual age is within += 10 seconds */ int64_t delta = (now - issue_at) - (identity->obfuscated_ticket_age - age_add); if (delta < 0) delta = -delta; if (tls->ctx->max_early_data_size != 0 && delta <= PTLS_EARLY_DATA_MAX_DELAY) *accept_early_data = 1; } /* check server-name */ if (ticket_server_name.len != 0) { if (tls->server_name == NULL) continue; if (!vec_is_string(ticket_server_name, tls->server_name)) continue; } else { if (tls->server_name != NULL) continue; } { /* check key-exchange */ ptls_key_exchange_algorithm_t **a; for (a = tls->ctx->key_exchanges; *a != NULL && (*a)->id != ticket_key_exchange_id; ++a) ; if (*a == NULL) continue; tls->key_share = *a; } /* check cipher-suite */ if (ticket_csid != tls->cipher_suite->id) continue; /* check negotiated-protocol */ if (ticket_negotiated_protocol.len != 0) { if (tls->negotiated_protocol == NULL) continue; if (!vec_is_string(ticket_negotiated_protocol, tls->negotiated_protocol)) continue; } /* check the length of the decrypted psk and the PSK binder */ if (ticket_psk.len != tls->key_schedule->hashes[0].algo->digest_size) continue; if (ch->psk.identities.list[*psk_index].binder.len != tls->key_schedule->hashes[0].algo->digest_size) continue; /* found */ goto Found; } /* not found */ *psk_index = SIZE_MAX; *accept_early_data = 0; tls->key_share = NULL; ret = 0; goto Exit; Found: if ((ret = key_schedule_extract(tls->key_schedule, ticket_psk)) != 0) goto Exit; if ((ret = derive_secret(tls->key_schedule, binder_key, "res binder")) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, ch_trunc.base, ch_trunc.len, 0); if ((ret = calc_verify_data(binder_key /* to conserve space, reuse binder_key for storing verify_data */, tls->key_schedule, binder_key)) != 0) goto Exit; if (!ptls_mem_equal(ch->psk.identities.list[*psk_index].binder.base, binder_key, tls->key_schedule->hashes[0].algo->digest_size)) { ret = PTLS_ALERT_DECRYPT_ERROR; goto Exit; } ret = 0; Exit: ptls_buffer_dispose(&decbuf); ptls_clear_memory(binder_key, sizeof(binder_key)); return ret; } static int calc_cookie_signature(ptls_t *tls, ptls_handshake_properties_t *properties, ptls_key_exchange_algorithm_t *negotiated_group, ptls_iovec_t tbs, uint8_t *sig) { ptls_hash_algorithm_t *algo = tls->ctx->cipher_suites[0]->hash; ptls_hash_context_t *hctx; if ((hctx = ptls_hmac_create(algo, properties->server.cookie.key, algo->digest_size)) == NULL) return PTLS_ERROR_NO_MEMORY; #define UPDATE_BLOCK(p, _len) \ do { \ size_t len = (_len); \ assert(len < UINT8_MAX); \ uint8_t len8 = (uint8_t)len; \ hctx->update(hctx, &len8, 1); \ hctx->update(hctx, (p), len); \ } while (0) #define UPDATE16(_v) \ do { \ uint16_t v = (_v); \ uint8_t b[2] = {v >> 8, v & 0xff}; \ hctx->update(hctx, b, 2); \ } while (0) UPDATE_BLOCK(tls->client_random, sizeof(tls->client_random)); UPDATE_BLOCK(tls->server_name, tls->server_name != NULL ? strlen(tls->server_name) : 0); UPDATE16(tls->cipher_suite->id); UPDATE16(negotiated_group->id); UPDATE_BLOCK(properties->server.cookie.additional_data.base, properties->server.cookie.additional_data.len); UPDATE_BLOCK(tbs.base, tbs.len); #undef UPDATE_BLOCK #undef UPDATE16 hctx->final(hctx, sig, PTLS_HASH_FINAL_MODE_FREE); return 0; } static int certificate_type_exists(uint8_t *list, size_t count, uint8_t desired_type) { /* empty type list means that we default to x509 */ if (desired_type == PTLS_CERTIFICATE_TYPE_X509 && count == 0) return 1; for (size_t i = 0; i < count; i++) { if (list[i] == desired_type) return 1; } return 0; } static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, ptls_handshake_properties_t *properties) { #define EMIT_SERVER_HELLO(sched, fill_rand, extensions, post_action) \ do { \ size_t sh_start_off; \ ptls_push_message(emitter, NULL, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, { \ sh_start_off = emitter->buf->off - PTLS_HANDSHAKE_HEADER_SIZE; \ ptls_buffer_push16(emitter->buf, 0x0303 /* legacy version */); \ if ((ret = ptls_buffer_reserve(emitter->buf, PTLS_HELLO_RANDOM_SIZE)) != 0) \ goto Exit; \ do { \ fill_rand \ } while (0); \ emitter->buf->off += PTLS_HELLO_RANDOM_SIZE; \ ptls_buffer_push_block(emitter->buf, 1, \ { ptls_buffer_pushv(emitter->buf, ch->legacy_session_id.base, ch->legacy_session_id.len); }); \ ptls_buffer_push16(emitter->buf, tls->cipher_suite->id); \ ptls_buffer_push(emitter->buf, 0); \ ptls_buffer_push_block(emitter->buf, 2, { \ buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, \ { ptls_buffer_push16(emitter->buf, ch->selected_version); }); \ do { \ extensions \ } while (0); \ }); \ }); \ do { \ post_action \ } while (0); \ ptls__key_schedule_update_hash((sched), emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off, 0); \ } while (0) #define EMIT_HELLO_RETRY_REQUEST(sched, negotiated_group, additional_extensions, post_action) \ EMIT_SERVER_HELLO((sched), { memcpy(emitter->buf->base + emitter->buf->off, hello_retry_random, PTLS_HELLO_RANDOM_SIZE); }, \ { \ ptls_key_exchange_algorithm_t *_negotiated_group = (negotiated_group); \ if (_negotiated_group != NULL) { \ buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_KEY_SHARE, \ { ptls_buffer_push16(emitter->buf, _negotiated_group->id); }); \ } \ do { \ additional_extensions \ } while (0); \ }, \ post_action) struct st_ptls_client_hello_t *ch; struct { ptls_key_exchange_algorithm_t *algorithm; ptls_iovec_t peer_key; } key_share = {NULL}; struct { uint8_t *encoded_ch_inner; uint8_t *ch_outer_aad; ptls_buffer_t ch_inner; } ech = {NULL}; enum { HANDSHAKE_MODE_FULL, HANDSHAKE_MODE_PSK, HANDSHAKE_MODE_PSK_DHE } mode; size_t psk_index = SIZE_MAX; ptls_iovec_t pubkey = {0}, ecdh_secret = {0}; int accept_early_data = 0, is_second_flight = tls->state == PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO, ret; ptls_buffer_init(&ech.ch_inner, "", 0); if ((ch = malloc(sizeof(*ch))) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } *ch = (struct st_ptls_client_hello_t){.unknown_extensions = {{UINT16_MAX}}}; /* decode ClientHello */ if ((ret = decode_client_hello(tls->ctx, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties, tls)) != 0) goto Exit; if ((ret = check_client_hello_constraints(tls->ctx, ch, is_second_flight ? tls->client_random : NULL, 0, message, tls)) != 0) goto Exit; if (!is_second_flight) { memcpy(tls->client_random, ch->random_bytes, PTLS_HELLO_RANDOM_SIZE); log_client_random(tls); } else { /* consistency check for ECH extension in response to HRR */ if (tls->ech.aead != NULL) { if (ch->ech.payload.base == NULL) { ret = PTLS_ALERT_MISSING_EXTENSION; goto Exit; } if (!(ch->ech.config_id == tls->ech.config_id && ch->ech.cipher_suite.kdf == tls->ech.cipher->id.kdf && ch->ech.cipher_suite.aead == tls->ech.cipher->id.aead && ch->ech.enc.len == 0)) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } } } /* ECH */ if (ch->ech.payload.base != NULL) { if (ch->ech.type != PTLS_ECH_CLIENT_HELLO_TYPE_OUTER) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } if (!is_second_flight) tls->ech.offered = 1; /* obtain AEAD context for opening inner CH */ if (!is_second_flight && ch->ech.payload.base != NULL && tls->ctx->ech.server.create_opener != NULL) { if ((tls->ech.aead = tls->ctx->ech.server.create_opener->cb( tls->ctx->ech.server.create_opener, &tls->ech.kem, &tls->ech.cipher, tls, ch->ech.config_id, ch->ech.cipher_suite, ch->ech.enc, ptls_iovec_init(ech_info_prefix, sizeof(ech_info_prefix)))) != NULL) tls->ech.config_id = ch->ech.config_id; } if (!is_second_flight) { PTLS_PROBE(ECH_SELECTION, tls, tls->ech.aead != NULL); PTLS_LOG_CONN(ech_selection, tls, { PTLS_LOG_ELEMENT_BOOL(is_ech, tls->ech.aead != NULL); }); } if (tls->ech.aead != NULL) { /* now that AEAD context is available, create AAD and decrypt inner CH */ if ((ech.encoded_ch_inner = malloc(ch->ech.payload.len - tls->ech.aead->algo->tag_size)) == NULL || (ech.ch_outer_aad = malloc(message.len - PTLS_HANDSHAKE_HEADER_SIZE)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } memcpy(ech.ch_outer_aad, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.len - PTLS_HANDSHAKE_HEADER_SIZE); memset(ech.ch_outer_aad + (ch->ech.payload.base - (message.base + PTLS_HANDSHAKE_HEADER_SIZE)), 0, ch->ech.payload.len); if (ptls_aead_decrypt(tls->ech.aead, ech.encoded_ch_inner, ch->ech.payload.base, ch->ech.payload.len, is_second_flight, ech.ch_outer_aad, message.len - PTLS_HANDSHAKE_HEADER_SIZE) != SIZE_MAX) { tls->ech.accepted = 1; /* successfully decrypted EncodedCHInner, build CHInner */ if ((ret = rebuild_ch_inner(&ech.ch_inner, ech.encoded_ch_inner, ech.encoded_ch_inner + ch->ech.payload.len - tls->ech.aead->algo->tag_size, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE + ch->first_extension_at, message.base + message.len)) != 0) goto Exit; /* treat inner ch as the message being received, re-decode it */ message = ptls_iovec_init(ech.ch_inner.base, ech.ch_inner.off); *ch = (struct st_ptls_client_hello_t){.unknown_extensions = {{UINT16_MAX}}}; if ((ret = decode_client_hello(tls->ctx, ch, ech.ch_inner.base + PTLS_HANDSHAKE_HEADER_SIZE, ech.ch_inner.base + ech.ch_inner.off, properties, tls)) != 0) goto Exit; if ((ret = check_client_hello_constraints(tls->ctx, ch, is_second_flight ? tls->ech.inner_client_random : NULL, 1, message, tls)) != 0) goto Exit; if (!is_second_flight) memcpy(tls->ech.inner_client_random, ch->random_bytes, PTLS_HELLO_RANDOM_SIZE); } else if (is_second_flight) { /* decryption failure of inner CH in 2nd CH is fatal */ ret = PTLS_ALERT_DECRYPT_ERROR; goto Exit; } else { /* decryption failure of 1st CH indicates key mismatch; dispose of AEAD context to indicate adoption of outerCH */ ptls_aead_free(tls->ech.aead); tls->ech.aead = NULL; } } } else if (tls->ech.offered) { assert(is_second_flight); ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } if (tls->ctx->require_dhe_on_psk) ch->psk.ke_modes &= ~(1u << PTLS_PSK_KE_MODE_PSK); /* handle client_random, legacy_session_id, SNI, ESNI */ if (!is_second_flight) { if (ch->legacy_session_id.len != 0) tls->send_change_cipher_spec = 1; ptls_iovec_t server_name = {NULL}; if (ch->server_name.base != NULL) server_name = ch->server_name; if ((ret = call_on_client_hello_cb(tls, server_name, message, ch->cipher_suites, ch->alpn.list, ch->alpn.count, ch->signature_algorithms.list, ch->signature_algorithms.count, ch->cert_compression_algos.list, ch->cert_compression_algos.count, ch->server_certificate_types.list, ch->server_certificate_types.count, 0)) != 0) goto Exit; if (!certificate_type_exists(ch->server_certificate_types.list, ch->server_certificate_types.count, tls->ctx->use_raw_public_keys ? PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY : PTLS_CERTIFICATE_TYPE_X509)) { ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; goto Exit; } } else { if (ch->psk.early_data_indication) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } /* We compare SNI only when the value is saved by the on_client_hello callback. This should be OK because we are * ignoring the value unless the callback saves the server-name. */ if (tls->server_name != NULL) { size_t l = strlen(tls->server_name); if (!(ch->server_name.len == l && memcmp(ch->server_name.base, tls->server_name, l) == 0)) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } } } { /* select (or check) cipher-suite, create key_schedule */ ptls_cipher_suite_t *cs; if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority)) != 0) goto Exit; if (!is_second_flight) { tls->cipher_suite = cs; tls->key_schedule = key_schedule_new(cs, NULL, 0); } else { if (tls->cipher_suite != cs) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } } } /* select key_share */ if (key_share.algorithm == NULL && ch->key_shares.base != NULL) { const uint8_t *src = ch->key_shares.base, *const end = src + ch->key_shares.len; ptls_decode_block(src, end, 2, { if ((ret = select_key_share(&key_share.algorithm, &key_share.peer_key, tls->ctx->key_exchanges, &src, end, 0)) != 0) goto Exit; }); } if (!is_second_flight) { if (ch->cookie.all.len != 0 && key_share.algorithm != NULL) { { /* use cookie to check the integrity of the handshake, and update the context */ uint8_t sig[PTLS_MAX_DIGEST_SIZE]; size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) goto Exit; if (!(ch->cookie.signature.len == sigsize && ptls_mem_equal(ch->cookie.signature.base, sig, sigsize))) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } } /* integrity check passed; update states */ key_schedule_update_ch1hash_prefix(tls->key_schedule); ptls__key_schedule_update_hash(tls->key_schedule, ch->cookie.ch1_hash.base, ch->cookie.ch1_hash.len, 0); key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0)); /* ... reusing sendbuf to rebuild HRR for hash calculation */ size_t hrr_start = emitter->buf->off; EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, ch->cookie.sent_key_share ? key_share.algorithm : NULL, { buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_COOKIE, { ptls_buffer_pushv(emitter->buf, ch->cookie.all.base, ch->cookie.all.len); }); }, {}); emitter->buf->off = hrr_start; is_second_flight = 1; } else if (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry)) { /* send HelloRetryRequest */ if (ch->negotiated_groups.base == NULL) { ret = PTLS_ALERT_MISSING_EXTENSION; goto Exit; } ptls_key_exchange_algorithm_t *negotiated_group; if ((ret = select_negotiated_group(&negotiated_group, tls->ctx->key_exchanges, ch->negotiated_groups.base, ch->negotiated_groups.base + ch->negotiated_groups.len)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); assert(tls->key_schedule->generation == 0); /* Either send a stateless retry (w. cookies) or a stateful one. When sending the latter, run the state machine. At the * moment, stateless retry is disabled when ECH is used (do we need to support it?). */ int retry_uses_cookie = properties != NULL && properties->server.retry_uses_cookie && !ptls_is_ech_handshake(tls, NULL, NULL, NULL); if (!retry_uses_cookie) { key_schedule_transform_post_ch1hash(tls->key_schedule); key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0)); } size_t ech_confirm_off = 0; EMIT_HELLO_RETRY_REQUEST( tls->key_schedule, key_share.algorithm != NULL ? NULL : negotiated_group, { ptls_buffer_t *sendbuf = emitter->buf; if (ptls_is_ech_handshake(tls, NULL, NULL, NULL)) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, { if ((ret = ptls_buffer_reserve(sendbuf, PTLS_ECH_CONFIRM_LENGTH)) != 0) goto Exit; memset(sendbuf->base + sendbuf->off, 0, PTLS_ECH_CONFIRM_LENGTH); ech_confirm_off = sendbuf->off; sendbuf->off += PTLS_ECH_CONFIRM_LENGTH; }); } if (retry_uses_cookie) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, { ptls_buffer_push_block(sendbuf, 2, { /* push to-be-signed data */ size_t tbs_start = sendbuf->off; ptls_buffer_push_block(sendbuf, 2, { /* first block of the cookie data is the hash(ch1) */ ptls_buffer_push_block(sendbuf, 1, { size_t sz = tls->cipher_suite->hash->digest_size; if ((ret = ptls_buffer_reserve(sendbuf, sz)) != 0) goto Exit; key_schedule_extract_ch1hash(tls->key_schedule, sendbuf->base + sendbuf->off); sendbuf->off += sz; }); /* second is if we have sent key_share extension */ ptls_buffer_push(sendbuf, key_share.algorithm == NULL); /* we can add more data here */ }); size_t tbs_len = sendbuf->off - tbs_start; /* push the signature */ ptls_buffer_push_block(sendbuf, 1, { size_t sz = tls->ctx->cipher_suites[0]->hash->digest_size; if ((ret = ptls_buffer_reserve(sendbuf, sz)) != 0) goto Exit; if ((ret = calc_cookie_signature(tls, properties, negotiated_group, ptls_iovec_init(sendbuf->base + tbs_start, tbs_len), sendbuf->base + sendbuf->off)) != 0) goto Exit; sendbuf->off += sz; }); }); }); } }, { if (ech_confirm_off != 0 && (ret = ech_calc_confirmation( tls->key_schedule, emitter->buf->base + ech_confirm_off, tls->ech.inner_client_random, ECH_CONFIRMATION_HRR, ptls_iovec_init(emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off))) != 0) goto Exit; }); if (retry_uses_cookie) { if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; ret = PTLS_ERROR_STATELESS_RETRY; } else { tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO; if (ch->psk.early_data_indication) tls->server.early_data_skipped_bytes = 0; ret = PTLS_ERROR_IN_PROGRESS; } goto Exit; } } /* handle unknown extensions */ if ((ret = report_unknown_extensions(tls, properties, ch->unknown_extensions)) != 0) goto Exit; /* try psk handshake */ if (!is_second_flight && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && tls->ctx->encrypt_ticket != NULL && !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { goto Exit; } } /* If client authentication is enabled, we always force a full handshake. * TODO: Check for `post_handshake_auth` extension and if that is present, do not force full handshake! * Remove also the check `!require_client_authentication` above. * * adjust key_schedule, determine handshake mode */ if (psk_index == SIZE_MAX || tls->ctx->require_client_authentication) { ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); if (!is_second_flight) { assert(tls->key_schedule->generation == 0); key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0)); } mode = HANDSHAKE_MODE_FULL; if (properties != NULL) properties->server.selected_psk_binder.len = 0; } else { ptls__key_schedule_update_hash(tls->key_schedule, ch->psk.hash_end, message.base + message.len - ch->psk.hash_end, 0); if ((ch->psk.ke_modes & (1u << PTLS_PSK_KE_MODE_PSK)) != 0) { mode = HANDSHAKE_MODE_PSK; } else { assert((ch->psk.ke_modes & (1u << PTLS_PSK_KE_MODE_PSK_DHE)) != 0); mode = HANDSHAKE_MODE_PSK_DHE; } tls->is_psk_handshake = 1; if (properties != NULL) { ptls_iovec_t *selected = &ch->psk.identities.list[psk_index].binder; memcpy(properties->server.selected_psk_binder.base, selected->base, selected->len); properties->server.selected_psk_binder.len = selected->len; } } tls->server.can_send_session_ticket = ch->psk.ke_modes != 0; if (accept_early_data && tls->ctx->max_early_data_size != 0 && psk_index == 0) { if ((tls->pending_handshake_secret = malloc(PTLS_MAX_DIGEST_SIZE)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } if ((ret = derive_exporter_secret(tls, 1)) != 0) goto Exit; if ((ret = setup_traffic_protection(tls, 0, "c e traffic", 1, 0)) != 0) goto Exit; } /* run key-exchange, to obtain pubkey and secret */ if (mode != HANDSHAKE_MODE_PSK) { if (key_share.algorithm == NULL) { ret = ch->key_shares.base != NULL ? PTLS_ALERT_HANDSHAKE_FAILURE : PTLS_ALERT_MISSING_EXTENSION; goto Exit; } if ((ret = key_share.algorithm->exchange(key_share.algorithm, &pubkey, &ecdh_secret, key_share.peer_key)) != 0) goto Exit; tls->key_share = key_share.algorithm; } { /* send ServerHello */ size_t ech_confirm_off = 0; EMIT_SERVER_HELLO( tls->key_schedule, { tls->ctx->random_bytes(emitter->buf->base + emitter->buf->off, PTLS_HELLO_RANDOM_SIZE); /* when accepting CHInner, last 8 byte of SH.random is zero for the handshake transcript */ if (ptls_is_ech_handshake(tls, NULL, NULL, NULL)) { ech_confirm_off = emitter->buf->off + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH; memset(emitter->buf->base + ech_confirm_off, 0, PTLS_ECH_CONFIRM_LENGTH); } }, { ptls_buffer_t *sendbuf = emitter->buf; if (mode != HANDSHAKE_MODE_PSK) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, { ptls_buffer_push16(sendbuf, key_share.algorithm->id); ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); }); }); } if (mode != HANDSHAKE_MODE_FULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); }); } }, { if (ech_confirm_off != 0 && (ret = ech_calc_confirmation( tls->key_schedule, emitter->buf->base + ech_confirm_off, tls->ech.inner_client_random, ECH_CONFIRMATION_SERVER_HELLO, ptls_iovec_init(emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off))) != 0) goto Exit; }); } /* processing of ECH is complete; dispose state */ clear_ech(&tls->ech, 1); if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; /* create protection contexts for the handshake */ assert(tls->key_schedule->generation == 1); key_schedule_extract(tls->key_schedule, ecdh_secret); if ((ret = setup_traffic_protection(tls, 1, "s hs traffic", 2, 0)) != 0) goto Exit; if (tls->pending_handshake_secret != NULL) { if ((ret = derive_secret(tls->key_schedule, tls->pending_handshake_secret, "c hs traffic")) != 0) goto Exit; if (tls->ctx->update_traffic_key != NULL && (ret = tls->ctx->update_traffic_key->cb(tls->ctx->update_traffic_key, tls, 0, 2, tls->pending_handshake_secret)) != 0) goto Exit; } else { if ((ret = setup_traffic_protection(tls, 0, "c hs traffic", 2, 0)) != 0) goto Exit; if (ch->psk.early_data_indication) tls->server.early_data_skipped_bytes = 0; } /* send EncryptedExtensions */ ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, { ptls_buffer_t *sendbuf = emitter->buf; ptls_buffer_push_block(sendbuf, 2, { if (tls->server_name != NULL) { /* In this event, the server SHALL include an extension of type "server_name" in the (extended) server hello. * The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {}); } if (tls->ctx->use_raw_public_keys) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); }); } if (tls->negotiated_protocol != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, { ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_pushv(sendbuf, tls->negotiated_protocol, strlen(tls->negotiated_protocol)); }); }); }); } if (tls->pending_handshake_secret != NULL) buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {}); /* send ECH retry_configs, if ECH was offered by rejected, even though we (the server) could have accepted ECH */ if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL, NULL) && tls->ctx->ech.server.create_opener != NULL && tls->ctx->ech.server.retry_configs.len != 0) buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, { ptls_buffer_pushv(sendbuf, tls->ctx->ech.server.retry_configs.base, tls->ctx->ech.server.retry_configs.len); }); if ((ret = push_additional_extensions(properties, sendbuf)) != 0) goto Exit; }); }); if (mode == HANDSHAKE_MODE_FULL) { /* send certificate request if client authentication is activated */ if (tls->ctx->require_client_authentication) { ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST, { /* certificate_request_context, this field SHALL be zero length, unless the certificate * request is used for post-handshake authentication. */ ptls_buffer_t *sendbuf = emitter->buf; ptls_buffer_push(sendbuf, 0); /* extensions */ ptls_buffer_push_block(sendbuf, 2, { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, { if ((ret = push_signature_algorithms(tls->ctx->verify_certificate, sendbuf)) != 0) goto Exit; }); }); }); if (ret != 0) { goto Exit; } } /* send certificate */ if ((ret = send_certificate(tls, emitter, &ch->signature_algorithms, ptls_iovec_init(NULL, 0), ch->status_request, ch->cert_compression_algos.list, ch->cert_compression_algos.count)) != 0) goto Exit; /* send certificateverify, finished, and complete the handshake */ if ((ret = server_finish_handshake(tls, emitter, 1, &ch->signature_algorithms)) != 0) goto Exit; } else { /* send finished, and complete the handshake */ if ((ret = server_finish_handshake(tls, emitter, 0, NULL)) != 0) goto Exit; } Exit: free(pubkey.base); if (ecdh_secret.base != NULL) { ptls_clear_memory(ecdh_secret.base, ecdh_secret.len); free(ecdh_secret.base); } free(ech.encoded_ch_inner); free(ech.ch_outer_aad); ptls_buffer_dispose(&ech.ch_inner); free(ch); return ret; #undef EMIT_SERVER_HELLO #undef EMIT_HELLO_RETRY_REQUEST } static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify, struct st_ptls_signature_algorithms_t *signature_algorithms) { int ret; if (send_cert_verify) { if ((ret = send_certificate_verify(tls, emitter, signature_algorithms, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING)) != 0) { if (ret == PTLS_ERROR_ASYNC_OPERATION) { tls->state = PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY; } goto Exit; } } if ((ret = send_finished(tls, emitter)) != 0) goto Exit; assert(tls->key_schedule->generation == 2); if ((ret = key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0))) != 0) goto Exit; if ((ret = setup_traffic_protection(tls, 1, "s ap traffic", 3, 0)) != 0) goto Exit; if ((ret = derive_secret(tls->key_schedule, tls->server.pending_traffic_secret, "c ap traffic")) != 0) goto Exit; if ((ret = derive_exporter_secret(tls, 0)) != 0) goto Exit; if (tls->pending_handshake_secret != NULL) { if (tls->ctx->omit_end_of_early_data) { if ((ret = commission_handshake_secret(tls)) != 0) goto Exit; tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED; } else { tls->state = PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA; } } else if (tls->ctx->require_client_authentication) { tls->state = PTLS_STATE_SERVER_EXPECT_CERTIFICATE; } else { tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED; } /* send session ticket if necessary */ if (tls->server.can_send_session_ticket && tls->ctx->ticket_lifetime != 0) { if ((ret = send_session_ticket(tls, emitter)) != 0) goto Exit; } if (tls->ctx->require_client_authentication) { ret = PTLS_ERROR_IN_PROGRESS; } else { ret = 0; } Exit: return ret; } static int server_handle_end_of_early_data(ptls_t *tls, ptls_iovec_t message) { int ret; if ((ret = commission_handshake_secret(tls)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED; ret = PTLS_ERROR_IN_PROGRESS; Exit: return ret; } static int server_handle_finished(ptls_t *tls, ptls_iovec_t message) { int ret; if ((ret = verify_finished(tls, message)) != 0) return ret; memcpy(tls->traffic_protection.dec.secret, tls->server.pending_traffic_secret, sizeof(tls->server.pending_traffic_secret)); ptls_clear_memory(tls->server.pending_traffic_secret, sizeof(tls->server.pending_traffic_secret)); if ((ret = setup_traffic_protection(tls, 0, NULL, 3, 0)) != 0) return ret; ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); tls->state = PTLS_STATE_SERVER_POST_HANDSHAKE; return 0; } static int update_traffic_key(ptls_t *tls, int is_enc) { struct st_ptls_traffic_protection_t *tp = is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec; uint8_t secret[PTLS_MAX_DIGEST_SIZE]; int ret; ptls_hash_algorithm_t *hash = tls->key_schedule->hashes[0].algo; if ((ret = ptls_hkdf_expand_label(hash, secret, hash->digest_size, ptls_iovec_init(tp->secret, hash->digest_size), "traffic upd", ptls_iovec_init(NULL, 0), NULL)) != 0) goto Exit; memcpy(tp->secret, secret, sizeof(secret)); ret = setup_traffic_protection(tls, is_enc, NULL, 3, 1); Exit: ptls_clear_memory(secret, sizeof(secret)); return ret; } static int handle_key_update(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message) { const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len; int ret; /* validate */ if (end - src != 1 || *src > 1) return PTLS_ALERT_DECODE_ERROR; /* update receive key */ if ((ret = update_traffic_key(tls, 0)) != 0) return ret; if (*src) { if (tls->ctx->update_traffic_key != NULL) return PTLS_ALERT_UNEXPECTED_MESSAGE; tls->needs_key_update = 1; } return 0; } static int parse_record_header(struct st_ptls_record_t *rec, const uint8_t *src) { rec->type = src[0]; rec->version = ntoh16(src + 1); rec->length = ntoh16(src + 3); if (rec->length > (size_t)(rec->type == PTLS_CONTENT_TYPE_APPDATA ? PTLS_MAX_ENCRYPTED_RECORD_SIZE : PTLS_MAX_PLAINTEXT_RECORD_SIZE)) return PTLS_ALERT_DECODE_ERROR; return 0; } static int parse_record(ptls_t *tls, struct st_ptls_record_t *rec, const uint8_t *src, size_t *len) { int ret; assert(*len != 0); /* Check if the first byte is something that we can handle, otherwise do not bother parsing / buffering the entire record as it * is obviously broken. SSL 2.0 handshakes fall into this path as well. */ if (tls->recvbuf.rec.base == NULL) { uint8_t type = src[0]; switch (type) { case PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC: case PTLS_CONTENT_TYPE_ALERT: case PTLS_CONTENT_TYPE_HANDSHAKE: case PTLS_CONTENT_TYPE_APPDATA: break; default: return PTLS_ALERT_DECODE_ERROR; } } if (tls->recvbuf.rec.base == NULL && *len >= 5) { /* fast path */ if ((ret = parse_record_header(rec, src)) != 0) return ret; if (5 + rec->length <= *len) { rec->fragment = src + 5; *len = rec->length + 5; return 0; } } /* slow path */ const uint8_t *const end = src + *len; *rec = (struct st_ptls_record_t){0}; if (tls->recvbuf.rec.base == NULL) { ptls_buffer_init(&tls->recvbuf.rec, "", 0); if ((ret = ptls_buffer_reserve(&tls->recvbuf.rec, 5)) != 0) return ret; } /* fill and parse the header */ while (tls->recvbuf.rec.off < 5) { if (src == end) return PTLS_ERROR_IN_PROGRESS; tls->recvbuf.rec.base[tls->recvbuf.rec.off++] = *src++; } if ((ret = parse_record_header(rec, tls->recvbuf.rec.base)) != 0) return ret; /* fill the fragment */ size_t addlen = rec->length + 5 - tls->recvbuf.rec.off; if (addlen != 0) { if ((ret = ptls_buffer_reserve(&tls->recvbuf.rec, addlen)) != 0) return ret; if (addlen > (size_t)(end - src)) addlen = end - src; if (addlen != 0) { memcpy(tls->recvbuf.rec.base + tls->recvbuf.rec.off, src, addlen); tls->recvbuf.rec.off += addlen; src += addlen; } } /* set rec->fragment if a complete record has been parsed */ if (tls->recvbuf.rec.off == rec->length + 5) { rec->fragment = tls->recvbuf.rec.base + 5; ret = 0; } else { ret = PTLS_ERROR_IN_PROGRESS; } *len -= end - src; return ret; } static void update_open_count(ptls_context_t *ctx, ssize_t delta) { if (ctx->update_open_count != NULL) ctx->update_open_count->cb(ctx->update_open_count, delta); } static ptls_t *new_instance(ptls_context_t *ctx, int is_server) { ptls_t *tls; assert(ctx->get_time != NULL && "please set ctx->get_time to `&ptls_get_time`; see #92"); if ((tls = malloc(sizeof(*tls))) == NULL) return NULL; update_open_count(ctx, 1); *tls = (ptls_t){ctx}; tls->is_server = is_server; tls->send_change_cipher_spec = ctx->send_change_cipher_spec; tls->skip_tracing = ptls_default_skip_tracing; return tls; } ptls_t *ptls_client_new(ptls_context_t *ctx) { ptls_t *tls = new_instance(ctx, 0); tls->state = PTLS_STATE_CLIENT_HANDSHAKE_START; tls->ctx->random_bytes(tls->client_random, sizeof(tls->client_random)); log_client_random(tls); if (tls->send_change_cipher_spec) { tls->client.legacy_session_id = ptls_iovec_init(tls->client.legacy_session_id_buf, sizeof(tls->client.legacy_session_id_buf)); tls->ctx->random_bytes(tls->client.legacy_session_id.base, tls->client.legacy_session_id.len); } PTLS_PROBE(NEW, tls, 0); PTLS_LOG_CONN(new, tls, { PTLS_LOG_ELEMENT_BOOL(is_server, 0); }); return tls; } ptls_t *ptls_server_new(ptls_context_t *ctx) { ptls_t *tls = new_instance(ctx, 1); tls->state = PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO; tls->server.early_data_skipped_bytes = UINT32_MAX; PTLS_PROBE(NEW, tls, 1); PTLS_LOG_CONN(new, tls, { PTLS_LOG_ELEMENT_BOOL(is_server, 1); }); return tls; } static int export_tls12_params(ptls_buffer_t *output, int is_server, int session_reused, ptls_cipher_suite_t *cipher, const void *client_random, const char *server_name, ptls_iovec_t negotiated_protocol, const void *enc_key, const void *enc_iv, uint64_t enc_seq, uint64_t enc_record_iv, const void *dec_key, const void *dec_iv, uint64_t dec_seq) { int ret; ptls_buffer_push_block(output, 2, { ptls_buffer_push(output, is_server); ptls_buffer_push(output, session_reused); ptls_buffer_push16(output, PTLS_PROTOCOL_VERSION_TLS12); ptls_buffer_push16(output, cipher->id); ptls_buffer_pushv(output, client_random, PTLS_HELLO_RANDOM_SIZE); ptls_buffer_push_block(output, 2, { size_t len = server_name != NULL ? strlen(server_name) : 0; ptls_buffer_pushv(output, server_name, len); }); ptls_buffer_push_block(output, 2, { ptls_buffer_pushv(output, negotiated_protocol.base, negotiated_protocol.len); }); ptls_buffer_push_block(output, 2, { ptls_buffer_pushv(output, enc_key, cipher->aead->key_size); ptls_buffer_pushv(output, enc_iv, cipher->aead->tls12.fixed_iv_size); ptls_buffer_push64(output, enc_seq); if (cipher->aead->tls12.record_iv_size != 0) ptls_buffer_push64(output, enc_record_iv); ptls_buffer_pushv(output, dec_key, cipher->aead->key_size); ptls_buffer_pushv(output, dec_iv, cipher->aead->tls12.fixed_iv_size); ptls_buffer_push64(output, dec_seq); }); ptls_buffer_push_block(output, 2, {}); /* for future extensions */ }); Exit: return ret; } int ptls_build_tls12_export_params(ptls_context_t *ctx, ptls_buffer_t *output, int is_server, int session_reused, ptls_cipher_suite_t *cipher, const void *master_secret, const void *hello_randoms, uint64_t next_send_record_iv, const char *server_name, ptls_iovec_t negotiated_protocol) { assert(cipher->aead->tls12.fixed_iv_size + cipher->aead->tls12.record_iv_size != 0 || !"given cipher-suite supports TLS/1.2"); uint8_t key_block[(PTLS_MAX_SECRET_SIZE + PTLS_MAX_IV_SIZE) * 2]; size_t key_block_len = (cipher->aead->key_size + cipher->aead->tls12.fixed_iv_size) * 2; int ret; assert(key_block_len <= sizeof(key_block)); /* generate key block */ if ((ret = ptls_tls12_phash(cipher->hash, key_block, key_block_len, ptls_iovec_init(master_secret, PTLS_TLS12_MASTER_SECRET_SIZE), "key expansion", ptls_iovec_init(hello_randoms, PTLS_HELLO_RANDOM_SIZE * 2))) != 0) goto Exit; /* determine key locations */ struct { const void *key; const void *iv; } client_secret, server_secret, *enc_secret = is_server ? &server_secret : &client_secret, *dec_secret = is_server ? &client_secret : &server_secret; client_secret.key = key_block; server_secret.key = key_block + cipher->aead->key_size; client_secret.iv = key_block + cipher->aead->key_size * 2; server_secret.iv = key_block + cipher->aead->key_size * 2 + cipher->aead->tls12.fixed_iv_size; /* Serialize prams. Sequence number of the first application record is 1, because Finished is the only message sent after * ChangeCipherSpec. */ ret = export_tls12_params(output, is_server, session_reused, cipher, (uint8_t *)hello_randoms + PTLS_HELLO_RANDOM_SIZE, server_name, negotiated_protocol, enc_secret->key, enc_secret->iv, 1, next_send_record_iv, dec_secret->key, dec_secret->iv, 1); Exit: ptls_clear_memory(key_block, sizeof(key_block)); return ret; } int ptls_export(ptls_t *tls, ptls_buffer_t *output) { /* TODO add tls13 support */ if (!tls->traffic_protection.enc.tls12) return PTLS_ERROR_LIBRARY; ptls_iovec_t negotiated_protocol = ptls_iovec_init(tls->negotiated_protocol, tls->negotiated_protocol != NULL ? strlen(tls->negotiated_protocol) : 0); return export_tls12_params(output, tls->is_server, tls->is_psk_handshake, tls->cipher_suite, tls->client_random, tls->server_name, negotiated_protocol, tls->traffic_protection.enc.secret, tls->traffic_protection.enc.secret + PTLS_MAX_SECRET_SIZE, tls->traffic_protection.enc.seq, tls->traffic_protection.enc.tls12_enc_record_iv, tls->traffic_protection.dec.secret, tls->traffic_protection.dec.secret + PTLS_MAX_SECRET_SIZE, tls->traffic_protection.dec.seq); } static int build_tls12_traffic_protection(ptls_t *tls, int is_enc, const uint8_t **src, const uint8_t *const end) { struct st_ptls_traffic_protection_t *tp = is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec; if ((size_t)(end - *src) < tls->cipher_suite->aead->key_size + tls->cipher_suite->aead->tls12.fixed_iv_size + sizeof(uint64_t)) return PTLS_ALERT_DECODE_ERROR; /* set properties */ memcpy(tp->secret, *src, tls->cipher_suite->aead->key_size); *src += tls->cipher_suite->aead->key_size; memcpy(tp->secret + PTLS_MAX_SECRET_SIZE, *src, tls->cipher_suite->aead->tls12.fixed_iv_size); *src += tls->cipher_suite->aead->tls12.fixed_iv_size; if (ptls_decode64(&tp->seq, src, end) != 0) return PTLS_ALERT_DECODE_ERROR; if (is_enc && tls->cipher_suite->aead->tls12.record_iv_size != 0) { if (ptls_decode64(&tp->tls12_enc_record_iv, src, end) != 0) return PTLS_ALERT_DECODE_ERROR; } tp->tls12 = 1; /* instantiate aead */ if ((tp->aead = ptls_aead_new_direct(tls->cipher_suite->aead, is_enc, tp->secret, tp->secret + PTLS_MAX_SECRET_SIZE)) == NULL) return PTLS_ERROR_NO_MEMORY; return 0; } int ptls_import(ptls_context_t *ctx, ptls_t **tls, ptls_iovec_t params) { const uint8_t *src = params.base, *const end = src + params.len; uint16_t protocol_version, csid; int ret; *tls = NULL; /* TODO handle flags like psk_handshake, ech_handshake as we add support for TLS/1.3 import */ ptls_decode_block(src, end, 2, { /* instantiate, based on the is_server flag */ if (end - src < 2) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } if ((*tls = new_instance(ctx, *src++)) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } (*tls)->is_psk_handshake = *src++; /* determine protocol version and cipher suite */ if ((ret = ptls_decode16(&protocol_version, &src, end)) != 0) goto Exit; if ((ret = ptls_decode16(&csid, &src, end)) != 0) goto Exit; (*tls)->cipher_suite = ptls_find_cipher_suite(ctx->tls12_cipher_suites, csid); if ((*tls)->cipher_suite == NULL) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } /* other version-independent stuff */ if (end - src < PTLS_HELLO_RANDOM_SIZE) { ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } memcpy((*tls)->client_random, src, PTLS_HELLO_RANDOM_SIZE); src += PTLS_HELLO_RANDOM_SIZE; ptls_decode_open_block(src, end, 2, { if (src != end) { if ((ret = ptls_set_server_name(*tls, (const char *)src, end - src)) != 0) goto Exit; src = end; } }); ptls_decode_open_block(src, end, 2, { if (src != end) { if ((ret = ptls_set_negotiated_protocol(*tls, (const char *)src, end - src)) != 0) goto Exit; src = end; } }); /* version-dependent stuff */ ptls_decode_open_block(src, end, 2, { switch (protocol_version) { case PTLS_PROTOCOL_VERSION_TLS12: /* setup AEAD keys */ if ((ret = build_tls12_traffic_protection(*tls, 1, &src, end)) != 0) goto Exit; if ((ret = build_tls12_traffic_protection(*tls, 0, &src, end)) != 0) goto Exit; break; default: ret = PTLS_ALERT_ILLEGAL_PARAMETER; break; } }); /* extensions */ ptls_decode_open_block(src, end, 2, { src = end; /* unused */ }); }); (*tls)->state = ptls_is_server(*tls) ? PTLS_STATE_SERVER_POST_HANDSHAKE : PTLS_STATE_CLIENT_POST_HANDSHAKE; Exit: if (ret != 0) { if (*tls != NULL) { ptls_free(*tls); *tls = NULL; } } return ret; } void ptls_free(ptls_t *tls) { PTLS_PROBE0(FREE, tls); PTLS_LOG_CONN(free, tls, {}); ptls_buffer_dispose(&tls->recvbuf.rec); ptls_buffer_dispose(&tls->recvbuf.mess); free_exporter_master_secret(tls, 1); free_exporter_master_secret(tls, 0); if (tls->key_schedule != NULL) key_schedule_free(tls->key_schedule); if (tls->traffic_protection.dec.aead != NULL) ptls_aead_free(tls->traffic_protection.dec.aead); if (tls->traffic_protection.enc.aead != NULL) ptls_aead_free(tls->traffic_protection.enc.aead); free(tls->server_name); free(tls->negotiated_protocol); clear_ech(&tls->ech, tls->is_server); if (tls->is_server) { if (tls->server.async_job != NULL) tls->server.async_job->destroy_(tls->server.async_job); } else { if (tls->client.key_share_ctx != NULL) tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, 1, NULL, ptls_iovec_init(NULL, 0)); if (tls->client.certificate_request.context.base != NULL) free(tls->client.certificate_request.context.base); } if (tls->certificate_verify.cb != NULL) tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, 0, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0)); if (tls->pending_handshake_secret != NULL) { ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE); free(tls->pending_handshake_secret); } update_open_count(tls->ctx, -1); ptls_clear_memory(tls, sizeof(*tls)); free(tls); } ptls_context_t *ptls_get_context(ptls_t *tls) { return tls->ctx; } void ptls_set_context(ptls_t *tls, ptls_context_t *ctx) { update_open_count(ctx, 1); update_open_count(tls->ctx, -1); tls->ctx = ctx; } ptls_async_job_t *ptls_get_async_job(ptls_t *tls) { return tls->server.async_job; } ptls_iovec_t ptls_get_client_random(ptls_t *tls) { return ptls_iovec_init(tls->client_random, PTLS_HELLO_RANDOM_SIZE); } ptls_cipher_suite_t *ptls_get_cipher(ptls_t *tls) { return tls->cipher_suite; } uint16_t ptls_get_protocol_version(ptls_t *tls) { if (tls->traffic_protection.enc.tls12) return PTLS_PROTOCOL_VERSION_TLS12; return PTLS_PROTOCOL_VERSION_TLS13; } int ptls_get_traffic_keys(ptls_t *tls, int is_enc, uint8_t *key, uint8_t *iv, uint64_t *seq) { struct st_ptls_traffic_protection_t *ctx = is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec; int ret; if ((ret = get_traffic_keys(tls->cipher_suite->aead, tls->cipher_suite->hash, key, iv, ctx->secret, ptls_iovec_init(NULL, 0), NULL)) != 0) return ret; *seq = ctx->seq; return 0; } const char *ptls_get_server_name(ptls_t *tls) { return tls->server_name; } int ptls_set_server_name(ptls_t *tls, const char *server_name, size_t server_name_len) { char *duped = NULL; if (server_name != NULL && (duped = duplicate_as_str(server_name, server_name_len != 0 ? server_name_len : strlen(server_name))) == NULL) return PTLS_ERROR_NO_MEMORY; free(tls->server_name); tls->server_name = duped; return 0; } const char *ptls_get_negotiated_protocol(ptls_t *tls) { return tls->negotiated_protocol; } int ptls_set_negotiated_protocol(ptls_t *tls, const char *protocol, size_t protocol_len) { char *duped = NULL; if (protocol != NULL && (duped = duplicate_as_str(protocol, protocol_len != 0 ? protocol_len : strlen(protocol))) == NULL) return PTLS_ERROR_NO_MEMORY; free(tls->negotiated_protocol); tls->negotiated_protocol = duped; return 0; } int ptls_handshake_is_complete(ptls_t *tls) { return tls->state >= PTLS_STATE_POST_HANDSHAKE_MIN; } int ptls_is_psk_handshake(ptls_t *tls) { return tls->is_psk_handshake; } int ptls_is_ech_handshake(ptls_t *tls, uint8_t *config_id, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher) { if (tls->ech.accepted) { if (config_id != NULL) *config_id = tls->ech.config_id; if (kem != NULL) *kem = tls->ech.kem; if (cipher != NULL) *cipher = tls->ech.cipher; return 1; } return 0; } void **ptls_get_data_ptr(ptls_t *tls) { return &tls->data_ptr; } int ptls_skip_tracing(ptls_t *tls) { return tls->skip_tracing; } void ptls_set_skip_tracing(ptls_t *tls, int skip_tracing) { tls->skip_tracing = skip_tracing; } static int handle_client_handshake_message(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, int is_end_of_record, ptls_handshake_properties_t *properties) { uint8_t type = message.base[0]; int ret; switch (tls->state) { case PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO: case PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO: if (type == PTLS_HANDSHAKE_TYPE_SERVER_HELLO && is_end_of_record) { ret = client_handle_hello(tls, emitter, message, properties); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_CLIENT_EXPECT_ENCRYPTED_EXTENSIONS: if (type == PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS) { ret = client_handle_encrypted_extensions(tls, message, properties); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE: if (type == PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST) { ret = client_handle_certificate_request(tls, message, properties); break; } /* fall through */ case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE: switch (type) { case PTLS_HANDSHAKE_TYPE_CERTIFICATE: ret = client_handle_certificate(tls, message); break; case PTLS_HANDSHAKE_TYPE_COMPRESSED_CERTIFICATE: ret = client_handle_compressed_certificate(tls, message); break; default: ret = PTLS_ALERT_UNEXPECTED_MESSAGE; break; } break; case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY: if (type == PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY) { ret = client_handle_certificate_verify(tls, message); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_CLIENT_EXPECT_FINISHED: if (type == PTLS_HANDSHAKE_TYPE_FINISHED && is_end_of_record) { ret = client_handle_finished(tls, emitter, message); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_CLIENT_POST_HANDSHAKE: switch (type) { case PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET: ret = client_handle_new_session_ticket(tls, message); break; case PTLS_HANDSHAKE_TYPE_KEY_UPDATE: ret = handle_key_update(tls, emitter, message); break; default: ret = PTLS_ALERT_UNEXPECTED_MESSAGE; break; } break; default: assert(!"unexpected state"); ret = PTLS_ALERT_INTERNAL_ERROR; break; } PTLS_PROBE(RECEIVE_MESSAGE, tls, message.base[0], message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.len - PTLS_HANDSHAKE_HEADER_SIZE, ret); PTLS_LOG_CONN(receive_message, tls, { PTLS_LOG_ELEMENT_UNSIGNED(message, message.base[0]); PTLS_LOG_ELEMENT_UNSIGNED(len, message.len - PTLS_HANDSHAKE_HEADER_SIZE); PTLS_LOG_ELEMENT_SIGNED(result, ret); }); return ret; } static int handle_server_handshake_message(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, int is_end_of_record, ptls_handshake_properties_t *properties) { uint8_t type = message.base[0]; int ret; switch (tls->state) { case PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO: case PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO: if (type == PTLS_HANDSHAKE_TYPE_CLIENT_HELLO && is_end_of_record) { ret = server_handle_hello(tls, emitter, message, properties); } else { ret = PTLS_ALERT_HANDSHAKE_FAILURE; } break; case PTLS_STATE_SERVER_EXPECT_CERTIFICATE: if (type == PTLS_HANDSHAKE_TYPE_CERTIFICATE) { ret = server_handle_certificate(tls, message); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY: if (type == PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY) { ret = server_handle_certificate_verify(tls, message); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA: assert(!tls->ctx->omit_end_of_early_data); if (type == PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA) { ret = server_handle_end_of_early_data(tls, message); } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_STATE_SERVER_EXPECT_FINISHED: if (type == PTLS_HANDSHAKE_TYPE_FINISHED && is_end_of_record) { ret = server_handle_finished(tls, message); } else { ret = PTLS_ALERT_HANDSHAKE_FAILURE; } break; case PTLS_STATE_SERVER_POST_HANDSHAKE: switch (type) { case PTLS_HANDSHAKE_TYPE_KEY_UPDATE: ret = handle_key_update(tls, emitter, message); break; default: ret = PTLS_ALERT_UNEXPECTED_MESSAGE; break; } break; default: assert(!"unexpected state"); ret = PTLS_ALERT_INTERNAL_ERROR; break; } PTLS_PROBE(RECEIVE_MESSAGE, tls, message.base[0], message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.len - PTLS_HANDSHAKE_HEADER_SIZE, ret); PTLS_LOG_CONN(receive_message, tls, { PTLS_LOG_ELEMENT_UNSIGNED(message, message.base[0]); PTLS_LOG_ELEMENT_UNSIGNED(len, message.len - PTLS_HANDSHAKE_HEADER_SIZE); PTLS_LOG_ELEMENT_SIGNED(result, ret); }); return ret; } static int handle_alert(ptls_t *tls, const uint8_t *src, size_t len) { if (len != 2) return PTLS_ALERT_DECODE_ERROR; uint8_t desc = src[1]; /* all fatal alerts and USER_CANCELLED warning tears down the connection immediately, regardless of the transmitted level */ return PTLS_ALERT_TO_PEER_ERROR(desc); } static int message_buffer_is_overflow(ptls_context_t *ctx, size_t size) { if (ctx->max_buffer_size == 0) return 0; if (size <= ctx->max_buffer_size) return 0; return 1; } static int handle_handshake_record(ptls_t *tls, int (*cb)(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, int is_end_of_record, ptls_handshake_properties_t *properties), ptls_message_emitter_t *emitter, struct st_ptls_record_t *rec, ptls_handshake_properties_t *properties) { int ret; /* handshake */ if (rec->type != PTLS_CONTENT_TYPE_HANDSHAKE) return PTLS_ALERT_DECODE_ERROR; /* flatten the unhandled messages */ const uint8_t *src, *src_end; if (tls->recvbuf.mess.base == NULL) { src = rec->fragment; src_end = src + rec->length; } else { if (message_buffer_is_overflow(tls->ctx, tls->recvbuf.mess.off + rec->length)) return PTLS_ALERT_HANDSHAKE_FAILURE; if ((ret = ptls_buffer_reserve(&tls->recvbuf.mess, rec->length)) != 0) return ret; memcpy(tls->recvbuf.mess.base + tls->recvbuf.mess.off, rec->fragment, rec->length); tls->recvbuf.mess.off += rec->length; src = tls->recvbuf.mess.base; src_end = src + tls->recvbuf.mess.off; } /* handle the messages */ ret = PTLS_ERROR_IN_PROGRESS; while (src_end - src >= 4) { size_t mess_len = 4 + ntoh24(src + 1); if (src_end - src < (int)mess_len) break; ret = cb(tls, emitter, ptls_iovec_init(src, mess_len), src_end - src == mess_len, properties); switch (ret) { case 0: case PTLS_ERROR_ASYNC_OPERATION: case PTLS_ERROR_IN_PROGRESS: break; default: ptls_buffer_dispose(&tls->recvbuf.mess); return ret; } src += mess_len; } /* keep last partial message in buffer */ if (src != src_end) { size_t new_size = src_end - src; if (message_buffer_is_overflow(tls->ctx, new_size)) return PTLS_ALERT_HANDSHAKE_FAILURE; if (tls->recvbuf.mess.base == NULL) { ptls_buffer_init(&tls->recvbuf.mess, "", 0); if ((ret = ptls_buffer_reserve(&tls->recvbuf.mess, new_size)) != 0) return ret; memcpy(tls->recvbuf.mess.base, src, new_size); } else { memmove(tls->recvbuf.mess.base, src, new_size); } tls->recvbuf.mess.off = new_size; ret = PTLS_ERROR_IN_PROGRESS; } else { ptls_buffer_dispose(&tls->recvbuf.mess); } return ret; } static int handle_input(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_buffer_t *decryptbuf, const void *input, size_t *inlen, ptls_handshake_properties_t *properties) { struct st_ptls_record_t rec; int ret; /* extract the record */ if ((ret = parse_record(tls, &rec, input, inlen)) != 0) return ret; assert(rec.fragment != NULL); /* decrypt the record */ if (rec.type == PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) { if (tls->state < PTLS_STATE_POST_HANDSHAKE_MIN) { if (!(rec.length == 1 && rec.fragment[0] == 0x01)) return PTLS_ALERT_ILLEGAL_PARAMETER; } else { return PTLS_ALERT_HANDSHAKE_FAILURE; } ret = PTLS_ERROR_IN_PROGRESS; goto NextRecord; } if (tls->traffic_protection.dec.aead != NULL && rec.type != PTLS_CONTENT_TYPE_ALERT) { size_t decrypted_length; if (rec.type != PTLS_CONTENT_TYPE_APPDATA) return PTLS_ALERT_HANDSHAKE_FAILURE; if ((ret = ptls_buffer_reserve(decryptbuf, 5 + rec.length)) != 0) return ret; if ((ret = aead_decrypt(&tls->traffic_protection.dec, decryptbuf->base + decryptbuf->off, &decrypted_length, rec.fragment, rec.length)) != 0) { if (tls->is_server && tls->server.early_data_skipped_bytes != UINT32_MAX) goto ServerSkipEarlyData; return ret; } rec.length = decrypted_length; rec.fragment = decryptbuf->base + decryptbuf->off; /* skip padding */ for (; rec.length != 0; --rec.length) if (rec.fragment[rec.length - 1] != 0) break; if (rec.length == 0) return PTLS_ALERT_UNEXPECTED_MESSAGE; rec.type = rec.fragment[--rec.length]; } else if (rec.type == PTLS_CONTENT_TYPE_APPDATA && tls->is_server && tls->server.early_data_skipped_bytes != UINT32_MAX) { goto ServerSkipEarlyData; } if (tls->recvbuf.mess.base != NULL || rec.type == PTLS_CONTENT_TYPE_HANDSHAKE) { /* handshake record */ ret = handle_handshake_record(tls, tls->is_server ? handle_server_handshake_message : handle_client_handshake_message, emitter, &rec, properties); } else { /* handling of an alert or an application record */ switch (rec.type) { case PTLS_CONTENT_TYPE_APPDATA: if (tls->state >= PTLS_STATE_POST_HANDSHAKE_MIN) { decryptbuf->off += rec.length; ret = 0; } else if (tls->state == PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA) { if (tls->traffic_protection.dec.aead != NULL) decryptbuf->off += rec.length; ret = 0; } else { ret = PTLS_ALERT_UNEXPECTED_MESSAGE; } break; case PTLS_CONTENT_TYPE_ALERT: ret = handle_alert(tls, rec.fragment, rec.length); break; default: ret = PTLS_ALERT_UNEXPECTED_MESSAGE; break; } } NextRecord: ptls_buffer_dispose(&tls->recvbuf.rec); return ret; ServerSkipEarlyData: tls->server.early_data_skipped_bytes += (uint32_t)rec.length; if (tls->server.early_data_skipped_bytes > PTLS_MAX_EARLY_DATA_SKIP_SIZE) return PTLS_ALERT_HANDSHAKE_FAILURE; ret = PTLS_ERROR_IN_PROGRESS; goto NextRecord; } static int handle_input_tls12(ptls_t *tls, ptls_buffer_t *decryptbuf, const void *input, size_t *inlen) { struct st_ptls_record_t rec; int ret; /* extract the record, or bail out */ if ((ret = parse_record(tls, &rec, input, inlen)) != 0) return ret; assert(rec.fragment != NULL); const uint8_t *src = rec.fragment, *end = src + rec.length; uint64_t nonce; uint8_t aad[PTLS_TLS12_AAD_SIZE]; /* determine the nonce */ if (tls->traffic_protection.dec.aead->algo->tls12.record_iv_size != 0) { assert(tls->traffic_protection.dec.aead->algo->tls12.record_iv_size == 8); if ((ret = ptls_decode64(&nonce, &src, end)) != 0) goto Exit; } else { nonce = tls->traffic_protection.dec.seq; } /* determine cleartext length */ size_t textlen = end - src; if (textlen < tls->traffic_protection.dec.aead->algo->tag_size) { ret = PTLS_ALERT_BAD_RECORD_MAC; goto Exit; } textlen -= tls->traffic_protection.dec.aead->algo->tag_size; /* build aad */ build_tls12_aad(aad, rec.type, tls->traffic_protection.dec.seq, (uint16_t)textlen); /* decrypt input to decryptbuf */ if ((ret = ptls_buffer_reserve(decryptbuf, textlen)) != 0) goto Exit; if (ptls_aead_decrypt(tls->traffic_protection.dec.aead, decryptbuf->base + decryptbuf->off, src, end - src, nonce, aad, sizeof(aad)) != textlen) { ret = PTLS_ALERT_BAD_RECORD_MAC; goto Exit; } ++tls->traffic_protection.dec.seq; /* record-type specific action */ switch (rec.type) { case PTLS_CONTENT_TYPE_APPDATA: /* if application data, retain the bytes being decrypted */ decryptbuf->off += textlen; break; case PTLS_CONTENT_TYPE_ALERT: /* submit alert without adjusting decryptbuf, so that the decrypted data would be dropped after handling the alert */ ret = handle_alert(tls, decryptbuf->base + decryptbuf->off, textlen); break; default: ret = PTLS_ALERT_UNEXPECTED_MESSAGE; break; } Exit: ptls_buffer_dispose(&tls->recvbuf.rec); ptls_clear_memory(aad, sizeof(aad)); return 0; } static void init_record_message_emitter(ptls_t *tls, struct st_ptls_record_message_emitter_t *emitter, ptls_buffer_t *sendbuf) { *emitter = (struct st_ptls_record_message_emitter_t){ {sendbuf, &tls->traffic_protection.enc, 5, begin_record_message, commit_record_message}}; } int ptls_handshake(ptls_t *tls, ptls_buffer_t *_sendbuf, const void *input, size_t *inlen, ptls_handshake_properties_t *properties) { struct st_ptls_record_message_emitter_t emitter; int ret; assert(tls->state < PTLS_STATE_POST_HANDSHAKE_MIN); init_record_message_emitter(tls, &emitter, _sendbuf); size_t sendbuf_orig_off = emitter.super.buf->off; /* special handlings */ switch (tls->state) { case PTLS_STATE_CLIENT_HANDSHAKE_START: { assert(input == NULL || *inlen == 0); assert(tls->ctx->key_exchanges[0] != NULL); return send_client_hello(tls, &emitter.super, properties, NULL); } case PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY: return server_finish_handshake(tls, &emitter.super, 1, NULL); default: break; } const uint8_t *src = input, *const src_end = src + *inlen; ptls_buffer_t decryptbuf; ptls_buffer_init(&decryptbuf, "", 0); /* perform handhake until completion or until all the input has been swallowed */ ret = PTLS_ERROR_IN_PROGRESS; while (ret == PTLS_ERROR_IN_PROGRESS && src != src_end) { size_t consumed = src_end - src; ret = handle_input(tls, &emitter.super, &decryptbuf, src, &consumed, properties); src += consumed; assert(decryptbuf.off == 0); } ptls_buffer_dispose(&decryptbuf); switch (ret) { case 0: case PTLS_ERROR_IN_PROGRESS: case PTLS_ERROR_STATELESS_RETRY: case PTLS_ERROR_ASYNC_OPERATION: break; default: /* Flush handshake messages that have been written partially. ECH_REQUIRED sticks out because it is a message sent * post-handshake compared to other alerts that are generating *during* the handshake. */ if (ret != PTLS_ALERT_ECH_REQUIRED) { ptls_clear_memory(emitter.super.buf->base + sendbuf_orig_off, emitter.super.buf->off - sendbuf_orig_off); emitter.super.buf->off = sendbuf_orig_off; } /* send alert immediately */ if (PTLS_ERROR_GET_CLASS(ret) != PTLS_ERROR_CLASS_PEER_ALERT) if (ptls_send_alert(tls, emitter.super.buf, PTLS_ALERT_LEVEL_FATAL, PTLS_ERROR_GET_CLASS(ret) == PTLS_ERROR_CLASS_SELF_ALERT ? ret : PTLS_ALERT_INTERNAL_ERROR) != 0) emitter.super.buf->off = sendbuf_orig_off; break; } *inlen -= src_end - src; return ret; } int ptls_receive(ptls_t *tls, ptls_buffer_t *decryptbuf, const void *_input, size_t *inlen) { const uint8_t *input = (const uint8_t *)_input, *const end = input + *inlen; size_t decryptbuf_orig_size = decryptbuf->off; int ret = 0; assert(tls->state >= PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA); /* loop until we decrypt some application data (or an error) */ while (ret == 0 && input != end && decryptbuf_orig_size == decryptbuf->off) { size_t consumed = end - input; if (tls->traffic_protection.dec.tls12) { ret = handle_input_tls12(tls, decryptbuf, input, &consumed); } else { ret = handle_input(tls, NULL, decryptbuf, input, &consumed, NULL); } input += consumed; switch (ret) { case 0: break; case PTLS_ERROR_IN_PROGRESS: ret = 0; break; case PTLS_ERROR_CLASS_PEER_ALERT + PTLS_ALERT_CLOSE_NOTIFY: /* TODO send close alert */ break; default: if (PTLS_ERROR_GET_CLASS(ret) == PTLS_ERROR_CLASS_SELF_ALERT) { /* TODO send alert */ } break; } } *inlen -= end - input; return ret; } static int update_send_key(ptls_t *tls, ptls_buffer_t *_sendbuf, int request_update) { struct st_ptls_record_message_emitter_t emitter; int ret; init_record_message_emitter(tls, &emitter, _sendbuf); size_t sendbuf_orig_off = emitter.super.buf->off; ptls_push_message(&emitter.super, NULL, PTLS_HANDSHAKE_TYPE_KEY_UPDATE, { ptls_buffer_push(emitter.super.buf, !!request_update); }); if ((ret = update_traffic_key(tls, 1)) != 0) goto Exit; ret = 0; Exit: if (ret != 0) emitter.super.buf->off = sendbuf_orig_off; return ret; } int ptls_send(ptls_t *tls, ptls_buffer_t *sendbuf, const void *input, size_t inlen) { assert(tls->traffic_protection.enc.aead != NULL); /* "For AES-GCM, up to 2^24.5 full-size records (about 24 million) may be encrypted on a given connection while keeping a * safety margin of approximately 2^-57 for Authenticated Encryption (AE) security." (RFC 8446 section 5.5) */ if (tls->traffic_protection.enc.seq >= 16777216) tls->needs_key_update = 1; if (tls->needs_key_update) { int ret; if ((ret = update_send_key(tls, sendbuf, tls->key_update_send_request)) != 0) return ret; tls->needs_key_update = 0; tls->key_update_send_request = 0; } return buffer_push_encrypted_records(sendbuf, PTLS_CONTENT_TYPE_APPDATA, input, inlen, &tls->traffic_protection.enc); } int ptls_update_key(ptls_t *tls, int request_update) { assert(tls->ctx->update_traffic_key == NULL); tls->needs_key_update = 1; tls->key_update_send_request = request_update; return 0; } size_t ptls_get_record_overhead(ptls_t *tls) { ptls_aead_algorithm_t *algo = tls->traffic_protection.enc.aead->algo; if (tls->traffic_protection.enc.tls12) { return 5 + algo->tls12.record_iv_size + algo->tag_size; } else { return 6 + algo->tag_size; } } int ptls_send_alert(ptls_t *tls, ptls_buffer_t *sendbuf, uint8_t level, uint8_t description) { size_t rec_start = sendbuf->off; int ret = 0; buffer_push_record(sendbuf, PTLS_CONTENT_TYPE_ALERT, { ptls_buffer_push(sendbuf, level, description); }); /* encrypt the alert if we have the encryption keys, unless when it is the early data key */ if (tls->traffic_protection.enc.aead != NULL && !(tls->state <= PTLS_STATE_CLIENT_EXPECT_FINISHED)) { if ((ret = buffer_encrypt_record(sendbuf, rec_start, &tls->traffic_protection.enc)) != 0) goto Exit; } Exit: return ret; } int ptls_export_secret(ptls_t *tls, void *output, size_t outlen, const char *label, ptls_iovec_t context_value, int is_early) { ptls_hash_algorithm_t *algo = tls->key_schedule->hashes[0].algo; uint8_t *master_secret = is_early ? tls->exporter_master_secret.early : tls->exporter_master_secret.one_rtt, derived_secret[PTLS_MAX_DIGEST_SIZE], context_value_hash[PTLS_MAX_DIGEST_SIZE]; int ret; if (master_secret == NULL) { if (is_early) { switch (tls->state) { case PTLS_STATE_CLIENT_HANDSHAKE_START: case PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO: ret = PTLS_ERROR_IN_PROGRESS; break; default: ret = PTLS_ERROR_NOT_AVAILABLE; break; } } else { ret = PTLS_ERROR_IN_PROGRESS; } return ret; } if ((ret = ptls_calc_hash(algo, context_value_hash, context_value.base, context_value.len)) != 0) return ret; if ((ret = ptls_hkdf_expand_label(algo, derived_secret, algo->digest_size, ptls_iovec_init(master_secret, algo->digest_size), label, ptls_iovec_init(algo->empty_digest, algo->digest_size), NULL)) != 0) goto Exit; ret = ptls_hkdf_expand_label(algo, output, outlen, ptls_iovec_init(derived_secret, algo->digest_size), "exporter", ptls_iovec_init(context_value_hash, algo->digest_size), NULL); Exit: ptls_clear_memory(derived_secret, sizeof(derived_secret)); ptls_clear_memory(context_value_hash, sizeof(context_value_hash)); return ret; } struct st_picotls_hmac_context_t { ptls_hash_context_t super; ptls_hash_algorithm_t *algo; ptls_hash_context_t *hash; uint8_t key[1]; }; static void hmac_update(ptls_hash_context_t *_ctx, const void *src, size_t len) { struct st_picotls_hmac_context_t *ctx = (struct st_picotls_hmac_context_t *)_ctx; ctx->hash->update(ctx->hash, src, len); } static void hmac_apply_key(struct st_picotls_hmac_context_t *ctx, uint8_t pad) { size_t i; for (i = 0; i != ctx->algo->block_size; ++i) ctx->key[i] ^= pad; ctx->hash->update(ctx->hash, ctx->key, ctx->algo->block_size); for (i = 0; i != ctx->algo->block_size; ++i) ctx->key[i] ^= pad; } static void hmac_final(ptls_hash_context_t *_ctx, void *md, ptls_hash_final_mode_t mode) { struct st_picotls_hmac_context_t *ctx = (struct st_picotls_hmac_context_t *)_ctx; assert(mode != PTLS_HASH_FINAL_MODE_SNAPSHOT || !"not supported"); if (md != NULL) { ctx->hash->final(ctx->hash, md, PTLS_HASH_FINAL_MODE_RESET); hmac_apply_key(ctx, 0x5c); ctx->hash->update(ctx->hash, md, ctx->algo->digest_size); } ctx->hash->final(ctx->hash, md, mode); switch (mode) { case PTLS_HASH_FINAL_MODE_FREE: ptls_clear_memory(ctx->key, ctx->algo->block_size); free(ctx); break; case PTLS_HASH_FINAL_MODE_RESET: hmac_apply_key(ctx, 0x36); break; default: assert(!"FIXME"); break; } } int ptls_calc_hash(ptls_hash_algorithm_t *algo, void *output, const void *src, size_t len) { ptls_hash_context_t *ctx; if ((ctx = algo->create()) == NULL) return PTLS_ERROR_NO_MEMORY; ctx->update(ctx, src, len); ctx->final(ctx, output, PTLS_HASH_FINAL_MODE_FREE); return 0; } ptls_hash_context_t *ptls_hmac_create(ptls_hash_algorithm_t *algo, const void *key, size_t key_size) { struct st_picotls_hmac_context_t *ctx; assert(key_size <= algo->block_size); if ((ctx = malloc(offsetof(struct st_picotls_hmac_context_t, key) + algo->block_size)) == NULL) return NULL; *ctx = (struct st_picotls_hmac_context_t){{hmac_update, hmac_final}, algo}; if ((ctx->hash = algo->create()) == NULL) { free(ctx); return NULL; } memset(ctx->key, 0, algo->block_size); memcpy(ctx->key, key, key_size); hmac_apply_key(ctx, 0x36); return &ctx->super; } int ptls_hkdf_extract(ptls_hash_algorithm_t *algo, void *output, ptls_iovec_t salt, ptls_iovec_t ikm) { ptls_hash_context_t *hash; if (salt.len == 0) salt = ptls_iovec_init(zeroes_of_max_digest_size, algo->digest_size); if ((hash = ptls_hmac_create(algo, salt.base, salt.len)) == NULL) return PTLS_ERROR_NO_MEMORY; hash->update(hash, ikm.base, ikm.len); hash->final(hash, output, PTLS_HASH_FINAL_MODE_FREE); return 0; } int ptls_hkdf_expand(ptls_hash_algorithm_t *algo, void *output, size_t outlen, ptls_iovec_t prk, ptls_iovec_t info) { ptls_hash_context_t *hmac = NULL; size_t i; uint8_t digest[PTLS_MAX_DIGEST_SIZE]; for (i = 0; (i * algo->digest_size) < outlen; ++i) { if (hmac == NULL) { if ((hmac = ptls_hmac_create(algo, prk.base, prk.len)) == NULL) return PTLS_ERROR_NO_MEMORY; } else { hmac->update(hmac, digest, algo->digest_size); } hmac->update(hmac, info.base, info.len); uint8_t gen = (uint8_t)(i + 1); hmac->update(hmac, &gen, 1); hmac->final(hmac, digest, 1); size_t off_start = i * algo->digest_size, off_end = off_start + algo->digest_size; if (off_end > outlen) off_end = outlen; memcpy((uint8_t *)output + off_start, digest, off_end - off_start); } if (hmac != NULL) hmac->final(hmac, NULL, PTLS_HASH_FINAL_MODE_FREE); ptls_clear_memory(digest, algo->digest_size); return 0; } int ptls_hkdf_expand_label(ptls_hash_algorithm_t *algo, void *output, size_t outlen, ptls_iovec_t secret, const char *label, ptls_iovec_t hash_value, const char *label_prefix) { ptls_buffer_t hkdf_label; uint8_t hkdf_label_buf[80]; int ret; ptls_buffer_init(&hkdf_label, hkdf_label_buf, sizeof(hkdf_label_buf)); ptls_buffer_push16(&hkdf_label, (uint16_t)outlen); ptls_buffer_push_block(&hkdf_label, 1, { if (label_prefix == NULL) label_prefix = PTLS_HKDF_EXPAND_LABEL_PREFIX; ptls_buffer_pushv(&hkdf_label, label_prefix, strlen(label_prefix)); ptls_buffer_pushv(&hkdf_label, label, strlen(label)); }); ptls_buffer_push_block(&hkdf_label, 1, { ptls_buffer_pushv(&hkdf_label, hash_value.base, hash_value.len); }); ret = ptls_hkdf_expand(algo, output, outlen, secret, ptls_iovec_init(hkdf_label.base, hkdf_label.off)); Exit: ptls_buffer_dispose(&hkdf_label); return ret; } int ptls_tls12_phash(ptls_hash_algorithm_t *algo, void *output, size_t outlen, ptls_iovec_t secret, const char *label, ptls_iovec_t seed) { ptls_hash_context_t *hmac; uint8_t An[PTLS_MAX_DIGEST_SIZE]; size_t output_off = 0; if ((hmac = ptls_hmac_create(algo, secret.base, secret.len)) == NULL) return PTLS_ERROR_NO_MEMORY; /* A(1) = HMAC_hash(secret, label + seed) */ if (label != NULL) hmac->update(hmac, label, strlen(label)); hmac->update(hmac, seed.base, seed.len); hmac->final(hmac, An, PTLS_HASH_FINAL_MODE_RESET); while (1) { /* output += HMAC_hash(secret, A(i) + label + seed) */ hmac->update(hmac, An, algo->digest_size); if (label != NULL) hmac->update(hmac, label, strlen(label)); hmac->update(hmac, seed.base, seed.len); if (outlen - output_off <= algo->digest_size) { /* digest of last chunk is at first written to An then the necessary bytes are copied to output */ hmac->final(hmac, An, PTLS_HASH_FINAL_MODE_FREE); memcpy((uint8_t *)output + output_off, An, outlen - output_off); break; } hmac->final(hmac, (uint8_t *)output + output_off, PTLS_HASH_FINAL_MODE_RESET); output_off += algo->digest_size; /* A(i) = HMAC_hash(secret, A(i-1)) */ hmac->update(hmac, An, algo->digest_size); hmac->final(hmac, An, PTLS_HASH_FINAL_MODE_RESET); } ptls_clear_memory(An, algo->digest_size); return 0; } ptls_cipher_context_t *ptls_cipher_new(ptls_cipher_algorithm_t *algo, int is_enc, const void *key) { ptls_cipher_context_t *ctx; if ((ctx = (ptls_cipher_context_t *)malloc(algo->context_size)) == NULL) return NULL; *ctx = (ptls_cipher_context_t){algo}; if (algo->setup_crypto(ctx, is_enc, key) != 0) { free(ctx); ctx = NULL; } return ctx; } void ptls_cipher_free(ptls_cipher_context_t *ctx) { ctx->do_dispose(ctx); free(ctx); } ptls_aead_context_t *new_aead(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret, ptls_iovec_t hash_value, const char *label_prefix) { ptls_aead_context_t *ctx = NULL; struct { uint8_t key[PTLS_MAX_SECRET_SIZE]; uint8_t iv[PTLS_MAX_IV_SIZE]; } key_iv; int ret; if ((ret = get_traffic_keys(aead, hash, key_iv.key, key_iv.iv, secret, hash_value, label_prefix)) != 0) goto Exit; ctx = ptls_aead_new_direct(aead, is_enc, key_iv.key, key_iv.iv); Exit: ptls_clear_memory(&key_iv, sizeof(key_iv)); return ctx; } ptls_aead_context_t *ptls_aead_new(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret, const char *label_prefix) { return new_aead(aead, hash, is_enc, secret, ptls_iovec_init(NULL, 0), label_prefix); } ptls_aead_context_t *ptls_aead_new_direct(ptls_aead_algorithm_t *aead, int is_enc, const void *key, const void *iv) { ptls_aead_context_t *ctx; if ((ctx = (ptls_aead_context_t *)malloc(aead->context_size)) == NULL) return NULL; *ctx = (ptls_aead_context_t){aead}; if (aead->setup_crypto(ctx, is_enc, key, iv) != 0) { free(ctx); return NULL; } return ctx; } void ptls_aead_free(ptls_aead_context_t *ctx) { ctx->dispose_crypto(ctx); free(ctx); } void ptls_aead_xor_iv(ptls_aead_context_t *ctx, const void *_bytes, size_t len) { const uint8_t *bytes = _bytes; uint8_t iv[PTLS_MAX_IV_SIZE]; ptls_aead_get_iv(ctx, iv); for (size_t i = 0; i < len; ++i) iv[i] ^= bytes[i]; ptls_aead_set_iv(ctx, iv); } void ptls_aead__build_iv(ptls_aead_algorithm_t *algo, uint8_t *iv, const uint8_t *static_iv, uint64_t seq) { size_t iv_size = algo->iv_size, i; const uint8_t *s = static_iv; uint8_t *d = iv; /* build iv */ for (i = iv_size - 8; i != 0; --i) *d++ = *s++; i = 64; do { i -= 8; *d++ = *s++ ^ (uint8_t)(seq >> i); } while (i != 0); } static void clear_memory(void *p, size_t len) { if (len != 0) memset(p, 0, len); } void (*volatile ptls_clear_memory)(void *p, size_t len) = clear_memory; static int mem_equal(const void *_x, const void *_y, size_t len) { const volatile uint8_t *x = _x, *y = _y; uint8_t t = 0; for (; len != 0; --len) t |= *x++ ^ *y++; return t == 0; } int (*volatile ptls_mem_equal)(const void *x, const void *y, size_t len) = mem_equal; static uint64_t get_time(ptls_get_time_t *self) { struct timeval tv; gettimeofday(&tv, NULL); return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; } ptls_get_time_t ptls_get_time = {get_time}; #if PICOTLS_USE_DTRACE PTLS_THREADLOCAL unsigned ptls_default_skip_tracing = 0; #endif int ptls_is_server(ptls_t *tls) { return tls->is_server; } struct st_ptls_raw_message_emitter_t { ptls_message_emitter_t super; size_t start_off; size_t *epoch_offsets; }; static int begin_raw_message(ptls_message_emitter_t *_self) { struct st_ptls_raw_message_emitter_t *self = (void *)_self; self->start_off = self->super.buf->off; return 0; } static int commit_raw_message(ptls_message_emitter_t *_self) { struct st_ptls_raw_message_emitter_t *self = (void *)_self; size_t epoch; /* epoch is the key epoch, with the only exception being 2nd CH generated after 0-RTT key */ epoch = self->super.enc->epoch; if (epoch == 1 && self->super.buf->base[self->start_off] == PTLS_HANDSHAKE_TYPE_CLIENT_HELLO) epoch = 0; for (++epoch; epoch < 5; ++epoch) { assert(self->epoch_offsets[epoch] == self->start_off); self->epoch_offsets[epoch] = self->super.buf->off; } self->start_off = SIZE_MAX; return 0; } size_t ptls_get_read_epoch(ptls_t *tls) { switch (tls->state) { case PTLS_STATE_CLIENT_HANDSHAKE_START: case PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO: case PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO: case PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO: case PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO: return 0; /* plaintext */ case PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA: assert(!tls->ctx->omit_end_of_early_data); return 1; /* 0-rtt */ case PTLS_STATE_CLIENT_EXPECT_ENCRYPTED_EXTENSIONS: case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE: case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE: case PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY: case PTLS_STATE_CLIENT_EXPECT_FINISHED: case PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY: case PTLS_STATE_SERVER_EXPECT_CERTIFICATE: case PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY: case PTLS_STATE_SERVER_EXPECT_FINISHED: return 2; /* handshake */ case PTLS_STATE_CLIENT_POST_HANDSHAKE: case PTLS_STATE_SERVER_POST_HANDSHAKE: return 3; /* 1-rtt */ default: assert(!"invalid state"); return SIZE_MAX; } } int ptls_handle_message(ptls_t *tls, ptls_buffer_t *sendbuf, size_t epoch_offsets[5], size_t in_epoch, const void *input, size_t inlen, ptls_handshake_properties_t *properties) { return tls->is_server ? ptls_server_handle_message(tls, sendbuf, epoch_offsets, in_epoch, input, inlen, properties) : ptls_client_handle_message(tls, sendbuf, epoch_offsets, in_epoch, input, inlen, properties); } int ptls_client_handle_message(ptls_t *tls, ptls_buffer_t *sendbuf, size_t epoch_offsets[5], size_t in_epoch, const void *input, size_t inlen, ptls_handshake_properties_t *properties) { assert(!tls->is_server); struct st_ptls_raw_message_emitter_t emitter = { {sendbuf, &tls->traffic_protection.enc, 0, begin_raw_message, commit_raw_message}, SIZE_MAX, epoch_offsets}; struct st_ptls_record_t rec = {PTLS_CONTENT_TYPE_HANDSHAKE, 0, inlen, input}; if (input == NULL) return send_client_hello(tls, &emitter.super, properties, NULL); if (ptls_get_read_epoch(tls) != in_epoch) return PTLS_ALERT_UNEXPECTED_MESSAGE; return handle_handshake_record(tls, handle_client_handshake_message, &emitter.super, &rec, properties); } int ptls_server_handle_message(ptls_t *tls, ptls_buffer_t *sendbuf, size_t epoch_offsets[5], size_t in_epoch, const void *input, size_t inlen, ptls_handshake_properties_t *properties) { assert(tls->is_server); struct st_ptls_raw_message_emitter_t emitter = { {sendbuf, &tls->traffic_protection.enc, 0, begin_raw_message, commit_raw_message}, SIZE_MAX, epoch_offsets}; struct st_ptls_record_t rec = {PTLS_CONTENT_TYPE_HANDSHAKE, 0, inlen, input}; if (tls->state == PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY) { assert(input == NULL || inlen == 0); return server_finish_handshake(tls, &emitter.super, 1, NULL); } assert(input != NULL); if (ptls_get_read_epoch(tls) != in_epoch) return PTLS_ALERT_UNEXPECTED_MESSAGE; return handle_handshake_record(tls, handle_server_handshake_message, &emitter.super, &rec, properties); } /** * checks if given name looks like an IP address */ int ptls_server_name_is_ipaddr(const char *name) { #ifdef AF_INET struct sockaddr_in sin; if (inet_pton(AF_INET, name, &sin) == 1) return 1; #endif #ifdef AF_INET6 struct sockaddr_in6 sin6; if (inet_pton(AF_INET6, name, &sin6) == 1) return 1; #endif return 0; } int ptls_ech_encode_config(ptls_buffer_t *buf, uint8_t config_id, ptls_hpke_kem_t *kem, ptls_iovec_t public_key, ptls_hpke_cipher_suite_t **ciphers, uint8_t max_name_length, const char *public_name) { int ret; ptls_buffer_push16(buf, PTLS_ECH_CONFIG_VERSION); ptls_buffer_push_block(buf, 2, { ptls_buffer_push(buf, config_id); ptls_buffer_push16(buf, kem->id); ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, public_key.base, public_key.len); }); ptls_buffer_push_block(buf, 2, { for (size_t i = 0; ciphers[i] != NULL; ++i) { ptls_buffer_push16(buf, ciphers[i]->id.kdf); ptls_buffer_push16(buf, ciphers[i]->id.aead); } }); ptls_buffer_push(buf, max_name_length); ptls_buffer_push_block(buf, 1, { ptls_buffer_pushv(buf, public_name, strlen(public_name)); }); ptls_buffer_push_block(buf, 2, {/* extensions */}); }); Exit: return ret; } static char *byte_to_hex(char *dst, uint8_t v) { *dst++ = "0123456789abcdef"[v >> 4]; *dst++ = "0123456789abcdef"[v & 0xf]; return dst; } char *ptls_hexdump(char *dst, const void *_src, size_t len) { char *buf = dst; const uint8_t *src = _src; for (size_t i = 0; i != len; ++i) dst = byte_to_hex(dst, src[i]); *dst = '\0'; return buf; } char *ptls_jsonescape(char *buf, const char *unsafe_str, size_t len) { char *dst = buf; const uint8_t *src = (const uint8_t *)unsafe_str, *end = src + len; for (; src != end; ++src) { switch (*src) { #define MAP(ch, escaped) \ case ch: \ memcpy(dst, (escaped), sizeof(escaped) - 1); \ dst += sizeof(escaped) - 1; \ break MAP('"', "\\\""); MAP('\\', "\\\\"); MAP('/', "\\/"); MAP('\b', "\\b"); MAP('\f', "\\f"); MAP('\n', "\\n"); MAP('\r', "\\r"); MAP('\t', "\\t"); #undef MAP default: if (*src < 0x20 || *src == 0x7f) { *dst++ = '\\'; *dst++ = 'u'; *dst++ = '0'; *dst++ = '0'; dst = byte_to_hex(dst, *src); } else { *dst++ = *src; } break; } } *dst = '\0'; return dst; } int ptls_log__do_pushv(ptls_buffer_t *buf, const void *p, size_t l) { if (ptls_buffer_reserve(buf, l) != 0) return 0; memcpy(buf->base + buf->off, p, l); buf->off += l; return 1; } int ptls_log__do_push_unsafestr(ptls_buffer_t *buf, const char *s, size_t l) { if (ptls_buffer_reserve(buf, l * (sizeof("\\uXXXX") - 1) + 1) != 0) return 0; buf->off = (uint8_t *)ptls_jsonescape((char *)(buf->base + buf->off), s, l) - buf->base; return 1; } int ptls_log__do_push_hexdump(ptls_buffer_t *buf, const void *s, size_t l) { if (ptls_buffer_reserve(buf, l * 2 + 1) != 0) return 0; ptls_hexdump((char *)(buf->base + buf->off), s, l); buf->off += l * 2; return 1; } int ptls_log__do_push_signed32(ptls_buffer_t *buf, int32_t v) { /* TODO optimize */ char s[sizeof("-2147483648")]; int len = snprintf(s, sizeof(s), "%" PRId32, v); return ptls_log__do_pushv(buf, s, (size_t)len); } int ptls_log__do_push_signed64(ptls_buffer_t *buf, int64_t v) { /* TODO optimize */ char s[sizeof("-9223372036854775808")]; int len = snprintf(s, sizeof(s), "%" PRId64, v); return ptls_log__do_pushv(buf, s, (size_t)len); } int ptls_log__do_push_unsigned32(ptls_buffer_t *buf, uint32_t v) { /* TODO optimize */ char s[sizeof("4294967295")]; int len = snprintf(s, sizeof(s), "%" PRIu32, v); return ptls_log__do_pushv(buf, s, (size_t)len); } int ptls_log__do_push_unsigned64(ptls_buffer_t *buf, uint64_t v) { /* TODO optimize */ char s[sizeof("18446744073709551615")]; int len = snprintf(s, sizeof(s), "%" PRIu64, v); return ptls_log__do_pushv(buf, s, (size_t)len); } #if PTLS_HAVE_LOG volatile ptls_log_t ptls_log = {}; static struct { int *fds; size_t num_fds; size_t num_lost; pthread_mutex_t mutex; } logctx = {.mutex = PTHREAD_MUTEX_INITIALIZER}; size_t ptls_log_num_lost(void) { return logctx.num_lost; } int ptls_log_add_fd(int fd) { int ret; pthread_mutex_lock(&logctx.mutex); int *newfds; if ((newfds = realloc(logctx.fds, sizeof(logctx.fds[0]) * (logctx.num_fds + 1))) == NULL) { ret = PTLS_ERROR_NO_MEMORY; goto Exit; } logctx.fds = newfds; logctx.fds[logctx.num_fds++] = fd; ptls_log.is_active = 1; ret = 0; /* success */ Exit: pthread_mutex_unlock(&logctx.mutex); return ret; } #endif void ptls_log__do_write(const ptls_buffer_t *buf) { #if PTLS_HAVE_LOG pthread_mutex_lock(&logctx.mutex); for (size_t fd_index = 0; fd_index < logctx.num_fds;) { ssize_t ret; while ((ret = write(logctx.fds[fd_index], buf->base, buf->off)) == -1 && errno == EINTR) ; if (ret == buf->off) { /* success */ ++fd_index; } else if (ret > 0 || (ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))) { /* partial write or buffer full */ ++logctx.num_lost; ++fd_index; } else { /* write error; close and remove that fd from array */ close(logctx.fds[fd_index]); logctx.fds[fd_index] = logctx.fds[logctx.num_fds - 1]; --logctx.num_fds; if (logctx.num_fds == 0) ptls_log.is_active = 0; } } pthread_mutex_unlock(&logctx.mutex); #endif }