| /* |
| * 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 "wincompat.h" |
| #endif |
| #include <assert.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #ifndef _WINDOWS |
| #include <errno.h> |
| #include <pthread.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <sys/time.h> |
| #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) { |
| if ((tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites, tls->ech.aead != NULL)) == NULL) { |
| ret = PTLS_ERROR_NO_MEMORY; |
| goto Exit; |
| } |
| 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; |
| }); |
| |
| /* In TLS versions 1.2 and earlier CH might not have an extensions block (or they might, see what OpenSSL 1.0.0 sends); so bail |
| * out if that is the case 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 (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; |
| if ((tls->key_schedule = key_schedule_new(cs, NULL, 0)) == NULL) { |
| ret = PTLS_ERROR_NO_MEMORY; |
| goto Exit; |
| } |
| } 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). |
| * |
| * Key updates do not happen with tls 1.2, check `key_schedule` to see if we are using tls/1.3 |
| */ |
| if (tls->traffic_protection.enc.seq >= 16777216 && tls->key_schedule != NULL) |
| 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 |
| } |