| /* |
| * 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. |
| */ |
| #include <assert.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #ifdef _WINDOWS |
| #include "wincompat.h" |
| #else |
| #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_ENCRYPTED_SERVER_NAME 0xffce |
| |
| #define PTLS_PROTOCOL_VERSION_TLS13_FINAL 0x0304 |
| #define PTLS_PROTOCOL_VERSION_TLS13_DRAFT26 0x7f1a |
| #define PTLS_PROTOCOL_VERSION_TLS13_DRAFT27 0x7f1b |
| #define PTLS_PROTOCOL_VERSION_TLS13_DRAFT28 0x7f1c |
| |
| #define PTLS_SERVER_NAME_TYPE_HOSTNAME 0 |
| |
| #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_FINAL, PTLS_PROTOCOL_VERSION_TLS13_DRAFT28, |
| PTLS_PROTOCOL_VERSION_TLS13_DRAFT27, PTLS_PROTOCOL_VERSION_TLS13_DRAFT26}; |
| |
| 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; |
| }; |
| |
| 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_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_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 |
| */ |
| uint8_t client_random[PTLS_HELLO_RANDOM_SIZE]; |
| /** |
| * esni |
| */ |
| ptls_esni_secret_t *esni; |
| /** |
| * exporter master secret (either 0rtt or 1rtt) |
| */ |
| struct { |
| uint8_t *early; |
| uint8_t *one_rtt; |
| } exporter_master_secret; |
| /* 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 */ |
| } 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_CLIENT_CIPHERS 32 |
| #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_cipher_suite_t *cipher; /* selected cipher-suite, or NULL if esni extension is not used */ |
| ptls_key_exchange_algorithm_t *key_share; |
| ptls_iovec_t peer_key; |
| const uint8_t *record_digest; |
| ptls_iovec_t encrypted_sni; |
| } esni; |
| struct { |
| ptls_iovec_t list[16]; |
| size_t count; |
| } alpn; |
| struct { |
| uint16_t list[16]; |
| size_t count; |
| } cert_compression_algos; |
| struct { |
| uint16_t list[MAX_CLIENT_CIPHERS]; |
| size_t count; |
| } client_ciphers; |
| 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 { |
| 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; |
| struct { |
| uint8_t list[MAX_CERTIFICATE_TYPES]; |
| size_t count; |
| } server_certificate_types; |
| ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1]; |
| unsigned status_request : 1; |
| }; |
| |
| 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; |
| } retry_request; |
| }; |
| }; |
| |
| struct st_ptls_key_schedule_t { |
| unsigned generation; /* early secret (1), hanshake secret (2), master secret (3) */ |
| const char *hkdf_label_prefix; |
| uint8_t secret[PTLS_MAX_DIGEST_SIZE]; |
| size_t num_hashes; |
| struct { |
| ptls_hash_algorithm_t *algo; |
| ptls_hash_context_t *ctx; |
| } 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 { |
| uint8_t bits[8]; /* only ids below 64 is tracked */ |
| }; |
| |
| static const uint8_t zeroes_of_max_digest_size[PTLS_MAX_DIGEST_SIZE] = {0}; |
| |
| static int 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); |
| 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 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 inline int extension_bitmap_is_set(struct st_ptls_extension_bitmap_t *bitmap, uint16_t id) |
| { |
| if (id < sizeof(bitmap->bits) * 8) |
| return (bitmap->bits[id / 8] & (1 << (id % 8))) != 0; |
| return 0; |
| } |
| |
| static inline void extension_bitmap_set(struct st_ptls_extension_bitmap_t *bitmap, uint16_t id) |
| { |
| if (id < sizeof(bitmap->bits) * 8) |
| bitmap->bits[id / 8] |= 1 << (id % 8); |
| } |
| |
| static inline void init_extension_bitmap(struct st_ptls_extension_bitmap_t *bitmap, uint8_t hstype) |
| { |
| *bitmap = (struct st_ptls_extension_bitmap_t){{0}}; |
| |
| #define EXT(extid, proc) \ |
| do { \ |
| int _found = 0; \ |
| do { \ |
| proc \ |
| } while (0); \ |
| if (!_found) \ |
| extension_bitmap_set(bitmap, PTLS_EXTENSION_TYPE_##extid); \ |
| } while (0) |
| #define ALLOW(allowed_hstype) _found = _found || hstype == PTLS_HANDSHAKE_TYPE_##allowed_hstype |
| |
| /* Implements the table found in section 4.2 of draft-19; "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." |
| */ |
| EXT(SERVER_NAME, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(ENCRYPTED_EXTENSIONS); |
| }); |
| EXT(STATUS_REQUEST, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(CERTIFICATE); |
| ALLOW(CERTIFICATE_REQUEST); |
| }); |
| EXT(SUPPORTED_GROUPS, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(ENCRYPTED_EXTENSIONS); |
| }); |
| EXT(SIGNATURE_ALGORITHMS, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(CERTIFICATE_REQUEST); |
| }); |
| EXT(ALPN, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(ENCRYPTED_EXTENSIONS); |
| }); |
| EXT(KEY_SHARE, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(SERVER_HELLO); |
| }); |
| EXT(PRE_SHARED_KEY, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(SERVER_HELLO); |
| }); |
| EXT(PSK_KEY_EXCHANGE_MODES, { ALLOW(CLIENT_HELLO); }); |
| EXT(EARLY_DATA, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(ENCRYPTED_EXTENSIONS); |
| ALLOW(NEW_SESSION_TICKET); |
| }); |
| EXT(COOKIE, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(SERVER_HELLO); |
| }); |
| EXT(SUPPORTED_VERSIONS, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(SERVER_HELLO); |
| }); |
| EXT(SERVER_CERTIFICATE_TYPE, { |
| ALLOW(CLIENT_HELLO); |
| ALLOW(ENCRYPTED_EXTENSIONS); |
| }); |
| |
| #undef ALLOW |
| #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 |
| |
| void ptls_buffer__release_memory(ptls_buffer_t *buf) |
| { |
| ptls_clear_memory(buf->base, buf->off); |
| if (buf->is_allocated) |
| free(buf->base); |
| } |
| |
| int ptls_buffer_reserve(ptls_buffer_t *buf, size_t delta) |
| { |
| if (buf->base == NULL) |
| return PTLS_ERROR_NO_MEMORY; |
| |
| if (PTLS_MEMORY_DEBUG || buf->capacity < buf->off + delta) { |
| uint8_t *newp; |
| size_t new_capacity = buf->capacity; |
| if (new_capacity < 1024) |
| new_capacity = 1024; |
| while (new_capacity < buf->off + delta) { |
| new_capacity *= 2; |
| } |
| 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; |
| } |
| |
| 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) |
| { |
| uint8_t aad[5]; |
| size_t off = 0; |
| |
| build_aad(aad, inlen + 1 + ctx->aead->algo->tag_size); |
| ptls_aead_encrypt_init(ctx->aead, ctx->seq++, aad, sizeof(aad)); |
| off += ptls_aead_encrypt_update(ctx->aead, ((uint8_t *)output) + off, input, inlen); |
| off += ptls_aead_encrypt_update(ctx->aead, ((uint8_t *)output) + off, &content_type, 1); |
| off += ptls_aead_encrypt_final(ctx->aead, ((uint8_t *)output) + off); |
| |
| return off; |
| } |
| |
| 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 */ |
| |
| #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; |
| buffer_push_record(buf, PTLS_CONTENT_TYPE_APPDATA, { |
| if ((ret = ptls_buffer_reserve(buf, chunk_size + enc->aead->algo->tag_size + 1)) != 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 */ |
| if (bodylen <= PTLS_MAX_PLAINTEXT_RECORD_SIZE) { |
| size_t overhead = 1 + enc->aead->algo->tag_size; |
| if ((ret = ptls_buffer_reserve(buf, overhead)) != 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; \ |
| init_extension_bitmap(&bitmap, (hstype)); \ |
| ptls_decode_open_block((src), end, 2, { \ |
| while ((src) != end) { \ |
| if ((ret = ptls_decode16((exttype), &(src), end)) != 0) \ |
| goto Exit; \ |
| if (extension_bitmap_is_set(&bitmap, *(exttype)) != 0) { \ |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; \ |
| goto Exit; \ |
| } \ |
| extension_bitmap_set(&bitmap, *(exttype)); \ |
| 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_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)); |
| |
| 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)); |
| } |
| |
| 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); |
| free(sched); |
| } |
| |
| static ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered, |
| const char *hkdf_label_prefix) |
| { |
| #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; |
| |
| if (hkdf_label_prefix == NULL) |
| hkdf_label_prefix = PTLS_HKDF_EXPAND_LABEL_PREFIX; |
| |
| { /* 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, hkdf_label_prefix}; |
| } |
| |
| /* 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; |
| ++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 = 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), |
| sched->hkdf_label_prefix)) != 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_one(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 (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; |
| } |
| |
| void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen) |
| { |
| size_t i; |
| |
| PTLS_DEBUGF("%s:%zu\n", __FUNCTION__, msglen); |
| for (i = 0; i != sched->num_hashes; ++i) |
| sched->hashes[i].ctx->update(sched->hashes[i].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)); |
| } |
| |
| static void key_schedule_extract_ch1hash(ptls_key_schedule_t *sched, uint8_t *hash) |
| { |
| 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) |
| { |
| uint8_t ch1hash[PTLS_MAX_DIGEST_SIZE]; |
| |
| key_schedule_extract_ch1hash(sched, ch1hash); |
| |
| key_schedule_update_ch1hash_prefix(sched); |
| ptls__key_schedule_update_hash(sched, ch1hash, sched->hashes[0].algo->digest_size); |
| } |
| |
| static int derive_secret_with_hash(ptls_key_schedule_t *sched, void *secret, const char *label, const uint8_t *hash) |
| { |
| int ret = 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), sched->hkdf_label_prefix); |
| 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 = 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, |
| sched->hkdf_label_prefix)) != 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 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; |
| |
| /* 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; |
| |
| 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)); |
| 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) |
| { |
| PTLS_PROBE(CLIENT_RANDOM, tls, |
| ptls_hexdump(alloca(sizeof(tls->client_random) * 2 + 1), 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 = 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), |
| sched->hkdf_label_prefix)) != 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); |
| |
| { /* 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 */ |
| ptls_buffer_init(&session_id, session_id_smallbuf, sizeof(session_id_smallbuf)); |
| 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 ptls_hash_context_t *create_sha256_context(ptls_context_t *ctx) |
| { |
| ptls_cipher_suite_t **cs; |
| |
| for (cs = ctx->cipher_suites; *cs != NULL; ++cs) { |
| switch ((*cs)->id) { |
| case PTLS_CIPHER_SUITE_AES_128_GCM_SHA256: |
| case PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256: |
| return (*cs)->hash->create(); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| 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) |
| { |
| 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) { |
| /* 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; |
| } |
| } |
| } |
| } |
| 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; |
| } |
| |
| static int parse_esni_keys(ptls_context_t *ctx, uint16_t *esni_version, ptls_key_exchange_algorithm_t **selected_key_share, |
| ptls_cipher_suite_t **selected_cipher, ptls_iovec_t *peer_key, uint16_t *padded_length, |
| char **published_sni, ptls_iovec_t input) |
| { |
| const uint8_t *src = input.base, *const end = input.base + input.len; |
| uint16_t version; |
| uint64_t not_before, not_after, now; |
| int ret = 0; |
| |
| /* version */ |
| if ((ret = ptls_decode16(&version, &src, end)) != 0) |
| goto Exit; |
| if (version != PTLS_ESNI_VERSION_DRAFT03) { |
| ret = PTLS_ALERT_DECODE_ERROR; |
| goto Exit; |
| } |
| |
| { /* verify checksum */ |
| ptls_hash_context_t *hctx; |
| uint8_t digest[PTLS_SHA256_DIGEST_SIZE]; |
| if (end - src < 4) { |
| ret = PTLS_ALERT_DECODE_ERROR; |
| goto Exit; |
| } |
| if ((hctx = create_sha256_context(ctx)) == NULL) { |
| ret = PTLS_ERROR_LIBRARY; |
| goto Exit; |
| } |
| hctx->update(hctx, input.base, src - input.base); |
| hctx->update(hctx, "\0\0\0\0", 4); |
| hctx->update(hctx, src + 4, end - (src + 4)); |
| hctx->final(hctx, digest, PTLS_HASH_FINAL_MODE_FREE); |
| if (memcmp(src, digest, 4) != 0) { |
| ret = PTLS_ALERT_DECODE_ERROR; |
| goto Exit; |
| } |
| src += 4; |
| } |
| *esni_version = version; |
| /* published sni */ |
| ptls_decode_open_block(src, end, 2, { |
| size_t len = end - src; |
| *published_sni = malloc(len + 1); |
| if (*published_sni == NULL) { |
| ret = PTLS_ERROR_NO_MEMORY; |
| goto Exit; |
| } |
| if (len > 0) { |
| memcpy(*published_sni, src, len); |
| } |
| (*published_sni)[len] = 0; |
| src = end; |
| }); |
| /* key-shares */ |
| ptls_decode_open_block(src, end, 2, { |
| if ((ret = select_key_share(selected_key_share, peer_key, ctx->key_exchanges, &src, end, 0)) != 0) |
| goto Exit; |
| }); |
| /* cipher-suite */ |
| ptls_decode_open_block(src, end, 2, { |
| if ((ret = select_cipher(selected_cipher, ctx->cipher_suites, src, end, ctx->server_cipher_preference)) != 0) |
| goto Exit; |
| src = end; |
| }); |
| /* padded-length */ |
| if ((ret = ptls_decode16(padded_length, &src, end)) != 0) |
| goto Exit; |
| if (padded_length == 0) |
| goto Exit; |
| /* not-before, not_after */ |
| if ((ret = ptls_decode64(¬_before, &src, end)) != 0 || (ret = ptls_decode64(¬_after, &src, end)) != 0) |
| goto Exit; |
| /* extensions */ |
| ptls_decode_block(src, end, 2, { |
| while (src != end) { |
| uint16_t id; |
| if ((ret = ptls_decode16(&id, &src, end)) != 0) |
| goto Exit; |
| ptls_decode_open_block(src, end, 2, { src = end; }); |
| } |
| }); |
| |
| /* check validity period */ |
| now = ctx->get_time->cb(ctx->get_time); |
| if (!(not_before * 1000 <= now && now <= not_after * 1000)) { |
| ret = PTLS_ALERT_DECODE_ERROR; |
| goto Exit; |
| } |
| |
| ret = 0; |
| Exit: |
| return ret; |
| } |
| |
| static int create_esni_aead(ptls_aead_context_t **aead_ctx, int is_enc, ptls_cipher_suite_t *cipher, ptls_iovec_t ecdh_secret, |
| const uint8_t *esni_contents_hash) |
| { |
| uint8_t aead_secret[PTLS_MAX_DIGEST_SIZE]; |
| int ret; |
| |
| if ((ret = ptls_hkdf_extract(cipher->hash, aead_secret, ptls_iovec_init(NULL, 0), ecdh_secret)) != 0) |
| goto Exit; |
| if ((*aead_ctx = new_aead(cipher->aead, cipher->hash, is_enc, aead_secret, |
| ptls_iovec_init(esni_contents_hash, cipher->hash->digest_size), "tls13 esni ")) == NULL) { |
| ret = PTLS_ERROR_NO_MEMORY; |
| goto Exit; |
| } |
| |
| ret = 0; |
| Exit: |
| ptls_clear_memory(aead_secret, sizeof(aead_secret)); |
| return ret; |
| } |
| |
| static int build_esni_contents_hash(ptls_hash_algorithm_t *hash, uint8_t *digest, const uint8_t *record_digest, uint16_t group, |
| ptls_iovec_t pubkey, const uint8_t *client_random) |
| { |
| ptls_buffer_t buf; |
| uint8_t smallbuf[256]; |
| int ret; |
| |
| /* build ESNIContents */ |
| ptls_buffer_init(&buf, smallbuf, sizeof(smallbuf)); |
| ptls_buffer_push_block(&buf, 2, { ptls_buffer_pushv(&buf, record_digest, hash->digest_size); }); |
| if ((ret = push_key_share_entry(&buf, group, pubkey)) != 0) |
| goto Exit; |
| ptls_buffer_pushv(&buf, client_random, PTLS_HELLO_RANDOM_SIZE); |
| |
| /* calculate digest */ |
| if ((ret = ptls_calc_hash(hash, digest, buf.base, buf.off)) != 0) |
| goto Exit; |
| |
| ret = 0; |
| Exit: |
| ptls_buffer_dispose(&buf); |
| return ret; |
| } |
| |
| static void free_esni_secret(ptls_esni_secret_t **esni, int is_server) |
| { |
| assert(*esni != NULL); |
| if ((*esni)->secret.base != NULL) { |
| ptls_clear_memory((*esni)->secret.base, (*esni)->secret.len); |
| free((*esni)->secret.base); |
| } |
| if (!is_server) |
| free((*esni)->client.pubkey.base); |
| ptls_clear_memory((*esni), sizeof(**esni)); |
| free(*esni); |
| *esni = NULL; |
| } |
| |
| static int client_setup_esni(ptls_context_t *ctx, ptls_esni_secret_t **esni, ptls_iovec_t esni_keys, char **published_sni, |
| const uint8_t *client_random) |
| { |
| ptls_iovec_t peer_key; |
| int ret; |
| |
| if ((*esni = malloc(sizeof(**esni))) == NULL) |
| return PTLS_ERROR_NO_MEMORY; |
| memset(*esni, 0, sizeof(**esni)); |
| |
| /* parse ESNI_Keys (and return success while keeping *esni NULL) */ |
| if (parse_esni_keys(ctx, &(*esni)->version, &(*esni)->client.key_share, &(*esni)->client.cipher, &peer_key, |
| &(*esni)->client.padded_length, published_sni, esni_keys) != 0) { |
| free(*esni); |
| *esni = NULL; |
| return 0; |
| } |
| |
| ctx->random_bytes((*esni)->nonce, sizeof((*esni)->nonce)); |
| |
| /* calc record digest */ |
| if ((ret = ptls_calc_hash((*esni)->client.cipher->hash, (*esni)->client.record_digest, esni_keys.base, esni_keys.len)) != 0) |
| goto Exit; |
| /* derive ECDH secret */ |
| if ((ret = (*esni)->client.key_share->exchange((*esni)->client.key_share, &(*esni)->client.pubkey, &(*esni)->secret, |
| peer_key)) != 0) |
| goto Exit; |
| /* calc H(ESNIContents) */ |
| if ((ret = build_esni_contents_hash((*esni)->client.cipher->hash, (*esni)->esni_contents_hash, (*esni)->client.record_digest, |
| (*esni)->client.key_share->id, (*esni)->client.pubkey, client_random)) != 0) |
| goto Exit; |
| |
| ret = 0; |
| Exit: |
| if (ret != 0) |
| free_esni_secret(esni, 0); |
| return ret; |
| } |
| |
| static int emit_esni_extension(ptls_esni_secret_t *esni, ptls_buffer_t *buf, ptls_iovec_t esni_keys, const char *server_name, |
| size_t key_share_ch_off, size_t key_share_ch_len) |
| { |
| ptls_aead_context_t *aead = NULL; |
| int ret; |
| |
| if ((ret = create_esni_aead(&aead, 1, esni->client.cipher, esni->secret, esni->esni_contents_hash)) != 0) |
| goto Exit; |
| |
| /* cipher-suite id */ |
| ptls_buffer_push16(buf, esni->client.cipher->id); |
| /* key-share */ |
| if ((ret = push_key_share_entry(buf, esni->client.key_share->id, esni->client.pubkey)) != 0) |
| goto Exit; |
| /* record-digest */ |
| ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, esni->client.record_digest, esni->client.cipher->hash->digest_size); }); |
| /* encrypted sni */ |
| ptls_buffer_push_block(buf, 2, { |
| size_t start_off = buf->off; |
| /* nonce */ |
| ptls_buffer_pushv(buf, esni->nonce, PTLS_ESNI_NONCE_SIZE); |
| /* emit server-name extension */ |
| if ((ret = emit_server_name_extension(buf, server_name)) != 0) |
| goto Exit; |
| /* pad */ |
| if (buf->off - start_off < (size_t)(esni->client.padded_length + PTLS_ESNI_NONCE_SIZE)) { |
| size_t bytes_to_pad = esni->client.padded_length + PTLS_ESNI_NONCE_SIZE - (buf->off - start_off); |
| if ((ret = ptls_buffer_reserve(buf, bytes_to_pad)) != 0) |
| goto Exit; |
| memset(buf->base + buf->off, 0, bytes_to_pad); |
| buf->off += bytes_to_pad; |
| } |
| /* encrypt */ |
| if ((ret = ptls_buffer_reserve(buf, aead->algo->tag_size)) != 0) |
| goto Exit; |
| ptls_aead_encrypt(aead, buf->base + start_off, buf->base + start_off, buf->off - start_off, 0, buf->base + key_share_ch_off, |
| key_share_ch_len); |
| buf->off += aead->algo->tag_size; |
| }); |
| |
| ret = 0; |
| Exit: |
| if (aead != NULL) |
| ptls_aead_free(aead); |
| 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; |
| char *published_sni = NULL; |
| uint32_t obfuscated_ticket_age = 0; |
| size_t msghash_off; |
| uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; |
| int ret, is_second_flight = tls->key_schedule != NULL, |
| send_sni = tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name); |
| |
| if (properties != NULL) { |
| /* try to use ESNI */ |
| if (!is_second_flight && send_sni && properties->client.esni_keys.base != NULL) { |
| if ((ret = client_setup_esni(tls->ctx, &tls->esni, properties->client.esni_keys, &published_sni, tls->client_random)) != |
| 0) { |
| goto Exit; |
| } |
| if (tls->ctx->update_esni_key != NULL) { |
| if ((ret = tls->ctx->update_esni_key->cb(tls->ctx->update_esni_key, tls, tls->esni->secret, |
| tls->esni->client.cipher->hash, tls->esni->esni_contents_hash)) != 0) |
| goto Exit; |
| } |
| } |
| /* 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]; |
| |
| if (!is_second_flight) { |
| tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites, tls->ctx->hkdf_label_prefix__obsolete); |
| if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0) |
| goto Exit; |
| } |
| |
| msghash_off = emitter->buf->off + emitter->record_header_length; |
| ptls_push_message(emitter, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, { |
| ptls_buffer_t *sendbuf = emitter->buf; |
| /* legacy_version */ |
| ptls_buffer_push16(sendbuf, 0x0303); |
| /* random_bytes */ |
| ptls_buffer_pushv(sendbuf, tls->client_random, sizeof(tls->client_random)); |
| /* lecagy_session_id */ |
| ptls_buffer_push_block( |
| sendbuf, 1, { ptls_buffer_pushv(sendbuf, tls->client.legacy_session_id.base, tls->client.legacy_session_id.len); }); |
| /* cipher_suites */ |
| ptls_buffer_push_block(sendbuf, 2, { |
| ptls_cipher_suite_t **cs = tls->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, { |
| struct { |
| size_t off; |
| size_t len; |
| } key_share_client_hello; |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, { |
| key_share_client_hello.off = sendbuf->off; |
| ptls_buffer_push_block(sendbuf, 2, { |
| if (tls->key_share != NULL) { |
| if ((ret = tls->key_share->create(tls->key_share, &tls->client.key_share_ctx)) != 0) |
| goto Exit; |
| if ((ret = push_key_share_entry(sendbuf, tls->key_share->id, tls->client.key_share_ctx->pubkey)) != 0) |
| goto Exit; |
| } |
| }); |
| key_share_client_hello.len = sendbuf->off - key_share_client_hello.off; |
| }); |
| if (send_sni) { |
| if (tls->esni != NULL) { |
| if (published_sni != NULL) { |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, { |
| if ((ret = emit_server_name_extension(sendbuf, published_sni)) != 0) |
| goto Exit; |
| }); |
| } |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME, { |
| if ((ret = emit_esni_extension(tls->esni, sendbuf, properties->client.esni_keys, tls->server_name, |
| key_share_client_hello.off, key_share_client_hello.len)) != 0) |
| goto Exit; |
| }); |
| } else { |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, { |
| if ((ret = emit_server_name_extension(sendbuf, tls->server_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 (tls->ctx->decompress_certificate != NULL) { |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE, { |
| ptls_buffer_push_block(sendbuf, 1, { |
| const uint16_t *algo = tls->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(tls->ctx->verify_certificate, sendbuf)) != 0) |
| goto Exit; |
| }); |
| buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, { |
| ptls_key_exchange_algorithm_t **algo = tls->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 (tls->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 (tls->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 (!tls->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 (tls->client.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, |
| { ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len); }); |
| ptls_buffer_push32(sendbuf, obfuscated_ticket_age); |
| }); |
| /* allocate space for PSK binder. the space is filled at the bottom of the function */ |
| ptls_buffer_push_block(sendbuf, 2, { |
| ptls_buffer_push_block(sendbuf, 1, { |
| if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->hashes[0].algo->digest_size)) != 0) |
| goto Exit; |
| sendbuf->off += tls->key_schedule->hashes[0].algo->digest_size; |
| }); |
| }); |
| }); |
| } |
| }); |
| }); |
| |
| /* 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); |
| 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); |
| |
| 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: |
| if (published_sni != NULL) { |
| free(published_sni); |
| } |
| ptls_clear_memory(binder_key, sizeof(binder_key)); |
| return ret; |
| } |
| |
| static ptls_cipher_suite_t *find_cipher_suite(ptls_context_t *ctx, uint16_t id) |
| { |
| ptls_cipher_suite_t **cs; |
| |
| for (cs = ctx->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 = find_cipher_suite(tls->ctx, csid)) == NULL) { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| goto Exit; |
| } |
| } |
| |
| /* legacy_compression_method */ |
| if (src == end || *src++ != 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, 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: |
| if (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; |
| }); |
| } else { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| goto Exit; |
| } |
| break; |
| case PTLS_EXTENSION_TYPE_PRE_SHARED_KEY: |
| if (sh->is_retry_request) { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| goto Exit; |
| } else { |
| if ((ret = ptls_decode16(&selected_psk_identity, &src, end)) != 0) |
| goto Exit; |
| } |
| 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; |
| } |
| |
| key_schedule_transform_post_ch1hash(tls->key_schedule); |
| ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len); |
| ret = send_client_hello(tls, emitter, properties, &sh->retry_request.cookie); |
| |
| Exit: |
| 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_one(tls->key_schedule, tls->cipher_suite, 0)) != 0) |
| goto Exit; |
| return handle_hello_retry_request(tls, emitter, &sh, message, properties); |
| } |
| |
| if ((ret = key_schedule_select_one(tls->key_schedule, tls->cipher_suite, tls->client.offered_psk && !tls->is_psk_handshake)) != |
| 0) |
| goto Exit; |
| |
| 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; |
| } |
| |
| ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len); |
| |
| 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, *esni_nonce = NULL; |
| 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_ENCRYPTED_SERVER_NAME: |
| if (*src == PTLS_ESNI_RESPONSE_TYPE_ACCEPT) { |
| if (end - src != PTLS_ESNI_NONCE_SIZE + 1) { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| goto Exit; |
| } |
| esni_nonce = src + 1; |
| } else { |
| /* TODO: provide API to parse the RETRY REQUEST response */ |
| ret = PTLS_ERROR_ESNI_RETRY; |
| 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; |
| 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->esni != NULL) { |
| if (esni_nonce == NULL || !ptls_mem_equal(esni_nonce, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE)) { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| goto Exit; |
| } |
| free_esni_secret(&tls->esni, 0); |
| } else { |
| if (esni_nonce != NULL) { |
| ret = PTLS_ALERT_ILLEGAL_PARAMETER; |
| 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); |
| 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: |
|