summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/deps/picotls/lib/picotls.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/server/h2o/libh2o/deps/picotls/lib/picotls.c3761
1 files changed, 3761 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/deps/picotls/lib/picotls.c b/web/server/h2o/libh2o/deps/picotls/lib/picotls.c
new file mode 100644
index 00000000..d328aaac
--- /dev/null
+++ b/web/server/h2o/libh2o/deps/picotls/lib/picotls.c
@@ -0,0 +1,3761 @@
+/*
+ * 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 <sys/time.h>
+#endif
+#include "picotls.h"
+
+#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_HELLO_RANDOM_SIZE 32
+
+#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_HANDSHAKE_TYPE_CLIENT_HELLO 1
+#define PTLS_HANDSHAKE_TYPE_SERVER_HELLO 2
+#define PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET 4
+#define PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA 5
+#define PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS 8
+#define PTLS_HANDSHAKE_TYPE_CERTIFICATE 11
+#define PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST 13
+#define PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY 15
+#define PTLS_HANDSHAKE_TYPE_FINISHED 20
+#define PTLS_HANDSHAKE_TYPE_KEY_UPDATE 24
+#define PTLS_HANDSHAKE_TYPE_MESSAGE_HASH 254
+
+#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_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_PROTOCOL_VERSION_DRAFT26 0x7f1a
+#define PTLS_PROTOCOL_VERSION_DRAFT27 0x7f1b
+#define PTLS_PROTOCOL_VERSION_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 */
+
+#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
+
+/**
+ * list of supported versions in the preferred order
+ */
+static const uint16_t supported_versions[] = {PTLS_PROTOCOL_VERSION_DRAFT28, PTLS_PROTOCOL_VERSION_DRAFT27,
+ PTLS_PROTOCOL_VERSION_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];
+ ptls_aead_context_t *aead;
+ uint64_t seq;
+};
+
+struct st_ptls_early_data_t {
+ uint8_t next_secret[PTLS_MAX_DIGEST_SIZE];
+};
+
+struct st_ptls_t {
+ /**
+ * the context
+ */
+ ptls_context_t *ctx;
+ /**
+ * the state
+ */
+ enum {
+ 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,
+ 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_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
+ */
+ struct st_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];
+ /* flags */
+ unsigned is_server : 1;
+ unsigned is_psk_handshake : 1;
+ unsigned skip_early_data : 1; /* if early-data is not recognized by the server */
+ unsigned send_change_cipher_spec : 1;
+ /**
+ * exporter master secret (either 0rtt or 1rtt)
+ */
+ struct {
+ uint8_t *early;
+ uint8_t *one_rtt;
+ } exporter_master_secret;
+ /**
+ * misc.
+ */
+ union {
+ struct {
+ uint8_t legacy_session_id[16];
+ ptls_key_exchange_context_t *key_share_ctx;
+ struct {
+ int (*cb)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t signature);
+ void *verify_ctx;
+ } certificate_verify;
+ unsigned offered_psk : 1;
+ } client;
+ struct {
+ uint8_t pending_traffic_secret[PTLS_MAX_DIGEST_SIZE];
+ } server;
+ };
+ /**
+ * the value contains the traffic secret to be commisioned after END_OF_EARLY_DATA
+ * END_OF_EARLY_DATA
+ */
+ struct st_ptls_early_data_t *early_data;
+ /**
+ * 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
+
+struct st_ptls_client_hello_t {
+ 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 {
+ uint16_t list[16]; /* expand? */
+ size_t count;
+ } signature_algorithms;
+ ptls_iovec_t server_name;
+ struct {
+ ptls_iovec_t list[16];
+ size_t count;
+ } alpn;
+ 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;
+ int early_data_indication;
+ } psk;
+ 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) */
+ 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 uint8_t zeroes_of_max_digest_size[PTLS_MAX_DIGEST_SIZE] = {0};
+
+static int is_supported_version(uint16_t v)
+{
+ size_t i;
+ for (i = 0; i != sizeof(supported_versions) / sizeof(supported_versions[0]); ++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);
+ });
+ EXT(SUPPORTED_GROUPS, {
+ ALLOW(CLIENT_HELLO);
+ ALLOW(ENCRYPTED_EXTENSIONS);
+ });
+ EXT(SIGNATURE_ALGORITHMS, { ALLOW(CLIENT_HELLO); });
+ 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);
+ });
+
+#undef ALLOW
+#undef EXT
+}
+
+static uint16_t ntoh16(const uint8_t *src)
+{
+ return (uint16_t)src[0] << 8 | src[1];
+}
+
+static uint32_t ntoh24(const uint8_t *src)
+{
+ return (uint32_t)src[0] << 16 | (uint32_t)src[1] << 8 | src[2];
+}
+
+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];
+}
+
+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];
+}
+
+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_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;
+}
+
+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;
+}
+
+#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;
+}
+
+#define buffer_push_handshake_body(buf, key_sched, type, block) \
+ do { \
+ size_t mess_start = (buf)->off; \
+ ptls_buffer_push((buf), (type)); \
+ ptls_buffer_push_block((buf), 3, { \
+ do { \
+ block \
+ } while (0); \
+ }); \
+ if ((key_sched) != NULL) \
+ key_schedule_update_hash((key_sched), (buf)->base + (mess_start), (buf)->off - (mess_start)); \
+ } while (0)
+
+#define buffer_push_handshake(buf, key_sched, enc, type, block) \
+ do { \
+ size_t rec_start = (buf)->off; \
+ buffer_push_record((buf), PTLS_CONTENT_TYPE_HANDSHAKE, \
+ { buffer_push_handshake_body((buf), (key_sched), (type), block); }); \
+ if ((enc) != NULL) { \
+ if ((ret = buffer_encrypt_record((buf), rec_start, (enc))) != 0) \
+ goto Exit; \
+ } \
+ } while (0)
+
+#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_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;
+}
+
+static void key_schedule_free(struct st_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 struct st_ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered)
+{
+#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)
+
+ struct st_ptls_key_schedule_t *sched;
+
+ { /* allocate */
+ size_t num_hashes = 0;
+ FOREACH_HASH({ ++num_hashes; });
+ if ((sched = malloc(offsetof(struct st_ptls_key_schedule_t, hashes) + sizeof(sched->hashes[0]) * num_hashes)) == NULL)
+ return NULL;
+ *sched = (struct st_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;
+ ++sched->num_hashes;
+ });
+
+ return sched;
+Fail:
+ key_schedule_free(sched);
+ return NULL;
+
+#undef FOREACH_HASH
+}
+
+static int key_schedule_extract(struct st_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_one(struct st_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;
+}
+
+static void key_schedule_update_hash(struct st_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(struct st_ptls_key_schedule_t *sched)
+{
+ uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)sched->hashes[0].algo->digest_size};
+ key_schedule_update_hash(sched, prefix, sizeof(prefix));
+}
+
+static void key_schedule_extract_ch1hash(struct st_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(struct st_ptls_key_schedule_t *sched)
+{
+ uint8_t ch1hash[PTLS_MAX_DIGEST_SIZE];
+
+ key_schedule_extract_ch1hash(sched, ch1hash);
+
+ key_schedule_update_ch1hash_prefix(sched);
+ key_schedule_update_hash(sched, ch1hash, sched->hashes[0].algo->digest_size);
+}
+
+static int derive_secret_with_hash(struct st_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(struct st_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(struct st_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)
+{
+ 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;
+ return derive_secret(tls->key_schedule, *slot, is_early ? "e exp master" : "exp master");
+}
+
+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(struct st_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(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, {
+ 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_context_t *ctx, 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(&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 = 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 = 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 = ctx->get_time->cb(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,
+ const char *base_label)
+{
+ return ptls_hkdf_expand_label(algo, key, key_size, ptls_iovec_init(secret, algo->digest_size), is_iv ? "iv" : "key",
+ ptls_iovec_init(NULL, 0), base_label);
+}
+
+static int setup_traffic_protection(ptls_t *tls, int is_enc, const char *secret_label, const char *log_label)
+{
+ 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;
+ }
+
+ 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, NULL)) == NULL)
+ return PTLS_ERROR_NO_MEMORY; /* TODO obtain error from ptls_aead_new */
+ ctx->seq = 0;
+
+ if (tls->ctx->log_secret != NULL)
+ tls->ctx->log_secret->cb(tls->ctx->log_secret, tls, log_label,
+ ptls_iovec_init(ctx->secret, tls->key_schedule->hashes[0].algo->digest_size));
+ PTLS_DEBUGF("[%s] %02x%02x,%02x%02x\n", log_label, (unsigned)ctx->secret[0], (unsigned)ctx->secret[1],
+ (unsigned)ctx->aead->static_iv[0], (unsigned)ctx->aead->static_iv[1]);
+
+ return 0;
+}
+
+static int retire_early_data_secret(ptls_t *tls, int is_enc)
+{
+ assert(tls->early_data != NULL);
+ memcpy((is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec)->secret, tls->early_data->next_secret,
+ PTLS_MAX_DIGEST_SIZE);
+ ptls_clear_memory(tls->early_data, sizeof(*tls->early_data));
+ free(tls->early_data);
+ tls->early_data = NULL;
+
+ return setup_traffic_protection(tls, is_enc, NULL, "CLIENT_HANDSHAKE_TRAFFIC_SECRET");
+}
+
+#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,
+ struct st_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, struct st_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, struct st_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 (memcmp(message.base + PTLS_HANDSHAKE_HEADER_SIZE, verify_data, tls->key_schedule->hashes[0].algo->digest_size) != 0) {
+ 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_buffer_t *sendbuf)
+{
+ int ret;
+
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_FINISHED, {
+ if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->hashes[0].algo->digest_size)) != 0)
+ goto Exit;
+ if ((ret = calc_verify_data(sendbuf->base + sendbuf->off, tls->key_schedule, tls->traffic_protection.enc.secret)) != 0)
+ goto Exit;
+ sendbuf->off += tls->key_schedule->hashes[0].algo->digest_size;
+ });
+
+Exit:
+ return ret;
+}
+
+static int send_session_ticket(ptls_t *tls, ptls_buffer_t *sendbuf)
+{
+ 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 = sendbuf->off;
+ if (tls->early_data != NULL) {
+ assert(tls->state == PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA);
+ buffer_push_handshake_body(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA, {});
+ sendbuf->off = orig_off;
+ }
+ buffer_push_handshake_body(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, {
+ if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->hashes[0].algo->digest_size)) != 0)
+ goto Exit;
+ if ((ret = calc_verify_data(sendbuf->base + sendbuf->off, tls->key_schedule,
+ tls->early_data != NULL ? tls->early_data->next_secret
+ : tls->traffic_protection.dec.secret)) != 0)
+ goto Exit;
+ sendbuf->off += tls->key_schedule->hashes[0].algo->digest_size;
+ });
+ sendbuf->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 */
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, {
+ ptls_buffer_push32(sendbuf, tls->ctx->ticket_lifetime);
+ ptls_buffer_push32(sendbuf, ticket_age_add);
+ ptls_buffer_push_block(sendbuf, 1, {});
+ ptls_buffer_push_block(sendbuf, 2, {
+ if ((ret = tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 1, sendbuf,
+ ptls_iovec_init(session_id.base, session_id.off))) != 0)
+ goto Exit;
+ });
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (tls->ctx->max_early_data_size != 0)
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA,
+ { ptls_buffer_push32(sendbuf, 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_buffer_t *sendbuf)
+{
+ int ret = 0;
+
+ if (!tls->send_change_cipher_spec)
+ goto Exit;
+ buffer_push_record(sendbuf, PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC, { ptls_buffer_push(sendbuf, 1); });
+ tls->send_change_cipher_spec = 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 send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake_properties_t *properties, ptls_iovec_t *cookie)
+{
+ ptls_iovec_t resumption_secret = {NULL}, resumption_ticket;
+ 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;
+
+ if (properties != NULL) {
+ /* 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->ctx, &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;
+ 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) {
+ *properties->client.max_early_data_size = max_early_data_size;
+ if ((tls->early_data = malloc(sizeof(*tls->early_data))) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ }
+ } else {
+ resumption_secret = ptls_iovec_init(NULL, 0);
+ }
+ }
+ if (properties->client.max_early_data_size != NULL && tls->early_data == NULL)
+ *properties->client.max_early_data_size = 0;
+ }
+
+ /* 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);
+ if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0)
+ goto Exit;
+ }
+
+ msghash_off = sendbuf->off + 5;
+ buffer_push_handshake(sendbuf, NULL, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
+ /* 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, sizeof(tls->client.legacy_session_id)); });
+ /* 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, {
+ if (tls->server_name != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ ptls_buffer_push(sendbuf, PTLS_SERVER_NAME_TYPE_HOSTNAME);
+ ptls_buffer_push_block(sendbuf, 2,
+ { ptls_buffer_pushv(sendbuf, tls->server_name, strlen(tls->server_name)); });
+ });
+ });
+ }
+ 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);
+ });
+ }
+ });
+ });
+ }
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ size_t i;
+ for (i = 0; i != sizeof(supported_versions) / sizeof(supported_versions[0]); ++i)
+ ptls_buffer_push16(sendbuf, supported_versions[i]);
+ });
+ });
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256);
+ ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256);
+ ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PKCS1_SHA256);
+ ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PKCS1_SHA1);
+ });
+ });
+ 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);
+ });
+ });
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (tls->key_share != NULL) {
+ ptls_iovec_t pubkey;
+ if ((ret = tls->key_share->create(&tls->client.key_share_ctx, &pubkey)) != 0)
+ goto Exit;
+ ptls_buffer_push16(sendbuf, tls->key_share->id);
+ ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); });
+ }
+ });
+ });
+ 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 ((ret = push_additional_extensions(properties, sendbuf)) != 0)
+ goto Exit;
+ if (tls->ctx->save_ticket != 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->early_data != NULL && !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 = sendbuf->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;
+ key_schedule_update_hash(tls->key_schedule, sendbuf->base + msghash_off, psk_binder_off - msghash_off);
+ msghash_off = psk_binder_off;
+ if ((ret = calc_verify_data(sendbuf->base + psk_binder_off + 3, tls->key_schedule, binder_key)) != 0)
+ goto Exit;
+ }
+ key_schedule_update_hash(tls->key_schedule, sendbuf->base + msghash_off, sendbuf->off - msghash_off);
+
+ if (tls->early_data != NULL) {
+ if ((ret = setup_traffic_protection(tls, 1, "c e traffic", "CLIENT_EARLY_TRAFFIC_SECRET")) != 0)
+ goto Exit;
+ if ((ret = push_change_cipher_spec(tls, sendbuf)) != 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_clear_memory(binder_key, sizeof(binder_key));
+ 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 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, {
+ 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_buffer_t *sendbuf, 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, NULL, ptls_iovec_init(NULL, 0));
+ tls->client.key_share_ctx = NULL;
+ }
+
+ 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);
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ret = send_client_hello(tls, sendbuf, properties, &sh->retry_request.cookie);
+
+Exit:
+ return ret;
+}
+
+static int client_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, 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 == sizeof(tls->client.legacy_session_id) &&
+ memcmp(sh.legacy_session_id.base, tls->client.legacy_session_id, sizeof(tls->client.legacy_session_id)) == 0)) {
+ 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, sendbuf, &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, &ecdh_secret, sh.peerkey)) != 0)
+ goto Exit;
+ }
+
+ 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", "SERVER_HANDSHAKE_TRAFFIC_SECRET")) != 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 handle_unknown_extension(ptls_t *tls, ptls_handshake_properties_t *properties, uint16_t type, const uint8_t *src,
+ const uint8_t *const end, ptls_raw_extension_t *slots)
+{
+
+ if (properties != NULL && properties->collect_extension != NULL && properties->collect_extension(tls, properties, type)) {
+ 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;
+ ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
+ int ret, skip_early_data = 1;
+
+ unknown_extensions[0].type = UINT16_MAX;
+
+ decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, &type, {
+ switch (type) {
+ case PTLS_EXTENSION_TYPE_SERVER_NAME:
+ if (src != end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ if (tls->server_name == NULL) {
+ 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 ((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->early_data == NULL) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ skip_early_data = 0;
+ break;
+ default:
+ handle_unknown_extension(tls, properties, type, src, end, unknown_extensions);
+ break;
+ }
+ src = end;
+ });
+
+ if (tls->early_data != NULL) {
+ tls->skip_early_data = skip_early_data;
+ if (properties != NULL && !skip_early_data)
+ properties->client.early_data_accepted_by_peer = 1;
+ if ((ret = derive_secret(tls->key_schedule, tls->early_data->next_secret, "c hs traffic")) != 0)
+ goto Exit;
+ } else {
+ if ((ret = setup_traffic_protection(tls, 1, "c hs traffic", "CLIENT_HANDSHAKE_TRAFFIC_SECRET")) != 0)
+ goto Exit;
+ }
+ if ((ret = report_unknown_extensions(tls, properties, unknown_extensions)) != 0)
+ goto Exit;
+
+ 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;
+ ret = PTLS_ERROR_IN_PROGRESS;
+
+Exit:
+ return ret;
+}
+
+static int client_handle_certificate(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 certs[16];
+ size_t num_certs = 0;
+ int ret;
+
+ /* 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, {
+ do {
+ ptls_decode_open_block(src, end, 3, {
+ if (num_certs < sizeof(certs) / sizeof(certs[0]))
+ certs[num_certs++] = ptls_iovec_init(src, end - src);
+ src = end;
+ });
+ uint16_t type;
+ decode_open_extensions(src, end, PTLS_HANDSHAKE_TYPE_CERTIFICATE, &type, { src = end; });
+ } while (src != end);
+ });
+
+ if (tls->ctx->verify_certificate != NULL) {
+ if ((ret = tls->ctx->verify_certificate->cb(tls->ctx->verify_certificate, tls, &tls->client.certificate_verify.cb,
+ &tls->client.certificate_verify.verify_ctx, certs, num_certs)) != 0)
+ goto Exit;
+ }
+
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY;
+ ret = PTLS_ERROR_IN_PROGRESS;
+
+Exit:
+ return ret;
+}
+
+static int client_handle_certificate_verify(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;
+ 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;
+ });
+
+ /* validate */
+ switch (algo) {
+ case PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256:
+ case PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256:
+ /* ok */
+ break;
+ default:
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ signdata_size = build_certificate_verify_signdata(signdata, tls->key_schedule, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING);
+ if (tls->client.certificate_verify.cb != NULL) {
+ ret = tls->client.certificate_verify.cb(tls->client.certificate_verify.verify_ctx, ptls_iovec_init(signdata, signdata_size),
+ signature);
+ } else {
+ ret = 0;
+ }
+ ptls_clear_memory(signdata, signdata_size);
+ tls->client.certificate_verify.cb = NULL;
+ if (ret != 0)
+ goto Exit;
+
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ tls->state = PTLS_STATE_CLIENT_EXPECT_FINISHED;
+ ret = PTLS_ERROR_IN_PROGRESS;
+
+Exit:
+ return ret;
+}
+
+static int client_handle_finished(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t message)
+{
+ uint8_t send_secret[PTLS_MAX_DIGEST_SIZE];
+ int ret;
+
+ if ((ret = verify_finished(tls, message)) != 0)
+ goto Exit;
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+
+ /* 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", "SERVER_TRAFFIC_SECRET_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->early_data != NULL) {
+ assert(tls->traffic_protection.enc.aead != NULL);
+ if (!tls->skip_early_data) {
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA,
+ {});
+ }
+ if ((ret = retire_early_data_secret(tls, 1)) != 0)
+ goto Exit;
+ }
+
+ if ((ret = push_change_cipher_spec(tls, sendbuf)) != 0)
+ goto Exit;
+ ret = send_finished(tls, sendbuf);
+
+ memcpy(tls->traffic_protection.enc.secret, send_secret, sizeof(send_secret));
+ if ((ret = setup_traffic_protection(tls, 1, NULL, "CLIENT_TRAFFIC_SECRET_0")) != 0)
+ goto Exit;
+
+ tls->state = PTLS_STATE_CLIENT_POST_HANDSHAKE;
+
+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(&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;
+ uint8_t ticket_buf_small[512];
+ ptls_buffer_init(&ticket_buf, ticket_buf_small, sizeof(ticket_buf_small));
+ 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_block(src, end, 2, {
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ do {
+ uint8_t type = *src++;
+ 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_cipher_suite(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **candidates, const uint8_t *src,
+ const uint8_t *const end)
+{
+ int ret;
+
+ ptls_decode_block(src, end, 2, {
+ while (src != end) {
+ uint16_t id;
+ if ((ret = ptls_decode16(&id, &src, end)) != 0)
+ goto Exit;
+ ptls_cipher_suite_t **c = candidates;
+ for (; *c != NULL; ++c) {
+ if ((*c)->id == id) {
+ *selected = *c;
+ return 0;
+ }
+ }
+ }
+ });
+
+ ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+
+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 ret;
+
+ ptls_decode_block(src, end, 2, {
+ 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 ((*c)->id == group) {
+ *selected = *c;
+ *peer_key = key;
+ return 0;
+ }
+ }
+ }
+ });
+
+ *selected = NULL;
+ ret = 0;
+
+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_t *tls, struct st_ptls_client_hello_t *ch, const uint8_t *src, const uint8_t *const end,
+ ptls_handshake_properties_t *properties)
+{
+ uint16_t exttype = 0;
+ int ret;
+
+ { /* check protocol version */
+ uint16_t protver;
+ if ((ret = ptls_decode16(&protver, &src, end)) != 0)
+ goto Exit;
+ if (protver != 0x0303) {
+ ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+ 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, {
+ ch->cipher_suites = ptls_iovec_init(src - 2, end - src + 2);
+ 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;
+ });
+
+ /* decode extensions */
+ decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, &exttype, {
+ switch (exttype) {
+ case PTLS_EXTENSION_TYPE_SERVER_NAME:
+ if ((ret = client_hello_decode_server_name(&ch->server_name, src, end)) != 0)
+ goto Exit;
+ break;
+ case PTLS_EXTENSION_TYPE_ALPN:
+ ptls_decode_block(src, end, 2, {
+ do {
+ ptls_decode_open_block(src, end, 1, {
+ if (ch->alpn.count < sizeof(ch->alpn.list) / sizeof(ch->alpn.list[0]))
+ ch->alpn.list[ch->alpn.count++] = ptls_iovec_init(src, end - src);
+ src = end;
+ });
+ } 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:
+ ptls_decode_block(src, end, 2, {
+ do {
+ uint16_t id;
+ if ((ret = ptls_decode16(&id, &src, end)) != 0)
+ goto Exit;
+ if (ch->signature_algorithms.count <
+ sizeof(ch->signature_algorithms.list) / sizeof(ch->signature_algorithms.list[0]))
+ ch->signature_algorithms.list[ch->signature_algorithms.count++] = id;
+ } while (src != end);
+ });
+ 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 = sizeof(supported_versions) / sizeof(supported_versions[0]);
+ 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 != sizeof(supported_versions) / sizeof(supported_versions[0]))
+ ch->selected_version = supported_versions[selected_index];
+ });
+ break;
+ case PTLS_EXTENSION_TYPE_COOKIE:
+ if (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;
+ });
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ switch (*src++) {
+ 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 < sizeof(ch->psk.identities.list) / sizeof(ch->psk.identities.list[0]))
+ 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;
+ }
+ });
+ } break;
+ case PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES:
+ ptls_decode_block(src, end, 1, {
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ for (; src != end; ++src) {
+ if (*src < sizeof(ch->psk.ke_modes) * 8)
+ ch->psk.ke_modes |= 1u << *src;
+ }
+ });
+ 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;
+ default:
+ handle_unknown_extension(tls, properties, exttype, src, end, ch->unknown_extensions);
+ break;
+ }
+ src = end;
+ });
+
+ /* check if client hello make sense */
+ if (is_supported_version(ch->selected_version)) {
+ if (!(ch->compression_methods.count == 1 && ch->compression_methods.ids[0] == 0)) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ /* pre-shared key */
+ if (ch->psk.hash_end != NULL) {
+ /* PSK must be the last extension */
+ if (exttype != PTLS_EXTENSION_TYPE_PRE_SHARED_KEY) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ } else {
+ if (ch->psk.early_data_indication) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ }
+ } else {
+ ret = PTLS_ALERT_PROTOCOL_VERSION;
+ goto Exit;
+ }
+
+ ret = 0;
+Exit:
+ return ret;
+}
+
+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 decbuf_small[256], binder_key[PTLS_MAX_DIGEST_SIZE], verify_data[PTLS_MAX_DIGEST_SIZE];
+ int ret;
+
+ ptls_buffer_init(&decbuf, decbuf_small, sizeof(decbuf_small));
+
+ 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 */
+ decbuf.off = 0;
+ if ((tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) != 0)
+ 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) {
+ int64_t delta = (now - issue_at) - (identity->obfuscated_ticket_age - age_add);
+ if (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;
+ key_schedule_update_hash(tls->key_schedule, ch_trunc.base, ch_trunc.len);
+ if ((ret = calc_verify_data(verify_data, tls->key_schedule, binder_key)) != 0)
+ goto Exit;
+ if (memcmp(ch->psk.identities.list[*psk_index].binder.base, verify_data, tls->key_schedule->hashes[0].algo->digest_size) != 0) {
+ ret = PTLS_ALERT_DECRYPT_ERROR;
+ goto Exit;
+ }
+ ret = 0;
+
+Exit:
+ ptls_buffer_dispose(&decbuf);
+ ptls_clear_memory(binder_key, sizeof(binder_key));
+ ptls_clear_memory(verify_data, sizeof(verify_data));
+ 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 server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t message, ptls_handshake_properties_t *properties)
+{
+#define EMIT_SERVER_HELLO(sched, fill_rand, extensions) \
+ buffer_push_handshake(sendbuf, (sched), NULL, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, { \
+ ptls_buffer_push16(sendbuf, 0x0303 /* legacy version */); \
+ if ((ret = ptls_buffer_reserve(sendbuf, PTLS_HELLO_RANDOM_SIZE)) != 0) \
+ goto Exit; \
+ do { \
+ fill_rand \
+ } while (0); \
+ sendbuf->off += PTLS_HELLO_RANDOM_SIZE; \
+ ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_pushv(sendbuf, ch.legacy_session_id.base, ch.legacy_session_id.len); }); \
+ ptls_buffer_push16(sendbuf, tls->cipher_suite->id); \
+ ptls_buffer_push(sendbuf, 0); \
+ ptls_buffer_push_block(sendbuf, 2, { \
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, \
+ { ptls_buffer_push16(sendbuf, ch.selected_version); }); \
+ do { \
+ extensions \
+ } while (0); \
+ }); \
+ });
+
+#define EMIT_HELLO_RETRY_REQUEST(sched, negotiated_group, additional_extensions) \
+ EMIT_SERVER_HELLO((sched), { memcpy(sendbuf->base + sendbuf->off, hello_retry_random, PTLS_HELLO_RANDOM_SIZE); }, \
+ { \
+ ptls_key_exchange_algorithm_t *_negotiated_group = (negotiated_group); \
+ if (_negotiated_group != NULL) { \
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, \
+ { ptls_buffer_push16(sendbuf, _negotiated_group->id); }); \
+ } \
+ do { \
+ additional_extensions \
+ } while (0); \
+ });
+
+ struct st_ptls_client_hello_t ch = {NULL, {NULL}, {NULL}, 0, {NULL}, {NULL}, {NULL},
+ {{0}}, {NULL}, {{{NULL}}}, {{NULL}}, {NULL}, {{UINT16_MAX}}};
+ struct {
+ ptls_key_exchange_algorithm_t *algorithm;
+ ptls_iovec_t peer_key;
+ } key_share = {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};
+ uint8_t finished_key[PTLS_MAX_DIGEST_SIZE];
+ int accept_early_data = 0, is_second_flight = tls->state == PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO, ret;
+
+ /* decode ClientHello */
+ if ((ret = decode_client_hello(tls, &ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties)) !=
+ 0)
+ goto Exit;
+ if (tls->ctx->require_dhe_on_psk)
+ ch.psk.ke_modes &= ~(1u << PTLS_PSK_KE_MODE_PSK);
+
+ /* handle client_random and SNI */
+ if (!is_second_flight) {
+ memcpy(tls->client_random, ch.random_bytes, sizeof(tls->client_random));
+ if (ch.server_name.base != NULL) {
+ if ((tls->server_name = malloc(ch.server_name.len + 1)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ memcpy(tls->server_name, ch.server_name.base, ch.server_name.len);
+ tls->server_name[ch.server_name.len] = '\0';
+ }
+ if (tls->ctx->on_client_hello != NULL &&
+ (ret = tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ch.server_name, ch.alpn.list, ch.alpn.count,
+ ch.signature_algorithms.list, ch.signature_algorithms.count)) != 0)
+ goto Exit;
+ } else {
+ if (ch.psk.early_data_indication) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ if (memcmp(tls->client_random, ch.random_bytes, sizeof(tls->client_random)) != 0 ||
+ (tls->server_name != NULL) != (ch.server_name.base != NULL) ||
+ (tls->server_name != NULL &&
+ !(strncmp(tls->server_name, (char *)ch.server_name.base, ch.server_name.len) == 0 &&
+ tls->server_name[ch.server_name.len] == '\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_suite(&cs, tls->ctx->cipher_suites, ch.cipher_suites.base,
+ ch.cipher_suites.base + ch.cipher_suites.len)) != 0)
+ goto Exit;
+ if (!is_second_flight) {
+ tls->cipher_suite = cs;
+ tls->key_schedule = key_schedule_new(cs, NULL);
+ } else {
+ if (tls->cipher_suite != cs) {
+ ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+ goto Exit;
+ }
+ }
+ }
+
+ /* select key_share */
+ if (ch.key_shares.base != NULL &&
+ (ret = select_key_share(&key_share.algorithm, &key_share.peer_key, tls->ctx->key_exchanges, ch.key_shares.base,
+ ch.key_shares.base + ch.key_shares.len)) != 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 && memcmp(ch.cookie.signature.base, sig, sigsize) == 0)) {
+ ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+ goto Exit;
+ }
+ /* integrity check passed; update states */
+ key_schedule_update_ch1hash_prefix(tls->key_schedule);
+ key_schedule_update_hash(tls->key_schedule, ch.cookie.ch1_hash.base, ch.cookie.ch1_hash.len);
+ key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
+ /* ... reusing sendbuf to rebuild HRR for hash calculation */
+ size_t hrr_start = sendbuf->off;
+ EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, ch.cookie.sent_key_share ? key_share.algorithm : NULL, {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE,
+ { ptls_buffer_pushv(sendbuf, ch.cookie.all.base, ch.cookie.all.len); });
+ });
+ sendbuf->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;
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ assert(tls->key_schedule->generation == 0);
+ if (properties != NULL && properties->server.retry_uses_cookie) {
+ /* emit HRR with cookie (note: we MUST omit KeyShare if the client has specified the correct one; see 46554f0) */
+ EMIT_HELLO_RETRY_REQUEST(NULL, key_share.algorithm != NULL ? NULL : negotiated_group, {
+ 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 ((ret = push_change_cipher_spec(tls, sendbuf)) != 0)
+ goto Exit;
+ ret = PTLS_ERROR_STATELESS_RETRY;
+ } else {
+ /* invoking stateful retry; roll the key schedule and emit HRR */
+ key_schedule_transform_post_ch1hash(tls->key_schedule);
+ key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
+ EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, key_share.algorithm != NULL ? NULL : negotiated_group, {});
+ if ((ret = push_change_cipher_spec(tls, sendbuf)) != 0)
+ goto Exit;
+ tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO;
+ if (ch.psk.early_data_indication)
+ tls->skip_early_data = 1;
+ 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) {
+ 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;
+ }
+
+ /* adjust key_schedule, determine handshake mode */
+ if (psk_index == SIZE_MAX) {
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ 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 {
+ key_schedule_update_hash(tls->key_schedule, ch.psk.hash_end, message.base + message.len - ch.psk.hash_end);
+ 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;
+ }
+ if ((ret = derive_exporter_secret(tls, 1)) != 0)
+ goto Exit;
+ }
+
+ if (accept_early_data && tls->ctx->max_early_data_size != 0 && psk_index == 0) {
+ if ((tls->early_data = malloc(sizeof(*tls->early_data))) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ if ((ret = setup_traffic_protection(tls, 0, "c e traffic", "CLIENT_EARLY_TRAFFIC_SECRET")) != 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(&pubkey, &ecdh_secret, key_share.peer_key)) != 0)
+ goto Exit;
+ tls->key_share = key_share.algorithm;
+ }
+
+ /* send ServerHello */
+ EMIT_SERVER_HELLO(tls->key_schedule, { tls->ctx->random_bytes(sendbuf->base + sendbuf->off, PTLS_HELLO_RANDOM_SIZE); },
+ {
+ 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 ((ret = push_change_cipher_spec(tls, sendbuf)) != 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", "SERVER_HANDSHAKE_TRAFFIC_SECRET")) != 0)
+ goto Exit;
+ if (tls->early_data != NULL) {
+ if ((ret = derive_secret(tls->key_schedule, tls->early_data->next_secret, "c hs traffic")) != 0)
+ goto Exit;
+ } else {
+ if ((ret = setup_traffic_protection(tls, 0, "c hs traffic", "CLIENT_HANDSHAKE_TRAFFIC_SECRET")) != 0)
+ goto Exit;
+ if (ch.psk.early_data_indication)
+ tls->skip_early_data = 1;
+ }
+
+ /* send EncryptedExtensions */
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, {
+ 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->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->early_data != NULL && tls->traffic_protection.dec.aead != NULL)
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
+ if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
+ goto Exit;
+ });
+ });
+
+ if (mode == HANDSHAKE_MODE_FULL) {
+ if (ch.signature_algorithms.count == 0) {
+ ret = PTLS_ALERT_MISSING_EXTENSION;
+ goto Exit;
+ }
+ /* send Certificate */
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_CERTIFICATE, {
+ ptls_buffer_push(sendbuf, 0);
+ ptls_buffer_push_block(sendbuf, 3, {
+ size_t i;
+ for (i = 0; i != tls->ctx->certificates.count; ++i) {
+ ptls_buffer_push_block(sendbuf, 3, {
+ ptls_buffer_pushv(sendbuf, tls->ctx->certificates.list[i].base, tls->ctx->certificates.list[i].len);
+ });
+ ptls_buffer_push_block(sendbuf, 2, {
+ /* emit OCSP stapling only when requested and when the callback successfully returns one */
+ if (ch.status_request && i == 0 && tls->ctx->staple_ocsp != NULL) {
+ size_t reset_off_to = sendbuf->off;
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, {
+ ptls_buffer_push(sendbuf, 1); /* status_type == ocsp */
+ ptls_buffer_push_block(sendbuf, 3, {
+ if ((ret = tls->ctx->staple_ocsp->cb(tls->ctx->staple_ocsp, tls, sendbuf, i)) == 0)
+ reset_off_to = 0;
+ });
+ });
+ if (reset_off_to != 0)
+ sendbuf->off = reset_off_to;
+ }
+ });
+ }
+ });
+ });
+ /* build and send CertificateVerify */
+ buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, {
+ 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, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING);
+ if ((ret = tls->ctx->sign_certificate->cb(tls->ctx->sign_certificate, tls, &algo, sendbuf,
+ ptls_iovec_init(data, datalen), ch.signature_algorithms.list,
+ ch.signature_algorithms.count)) != 0)
+ goto Exit;
+ sendbuf->base[algo_off] = (uint8_t)(algo >> 8);
+ sendbuf->base[algo_off + 1] = (uint8_t)algo;
+ });
+ });
+ }
+
+ send_finished(tls, sendbuf);
+
+ 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", "SERVER_TRAFFIC_SECRET_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;
+
+ tls->state = tls->early_data != NULL ? PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA : PTLS_STATE_SERVER_EXPECT_FINISHED;
+
+ /* send session ticket if necessary */
+ if (ch.psk.ke_modes != 0 && tls->ctx->ticket_lifetime != 0) {
+ if ((ret = send_session_ticket(tls, sendbuf)) != 0)
+ goto Exit;
+ }
+
+ ret = 0;
+
+Exit:
+ free(pubkey.base);
+ free(ecdh_secret.base);
+ ptls_clear_memory(finished_key, sizeof(finished_key));
+ return ret;
+
+#undef EMIT_SERVER_HELLO
+#undef EMIT_HELLO_RETRY_REQUEST
+}
+
+static int server_handle_end_of_early_data(ptls_t *tls, ptls_iovec_t message)
+{
+ int ret;
+
+ if ((ret = retire_early_data_secret(tls, 0)) != 0)
+ goto Exit;
+
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ 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, "CLIENT_TRAFFIC_SECRET_0")) != 0)
+ return ret;
+
+ key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+
+ tls->state = PTLS_STATE_SERVER_POST_HANDSHAKE;
+ 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;
+
+ 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);
+}
+
+ptls_t *ptls_new(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;
+ if (!is_server) {
+ tls->state = PTLS_STATE_CLIENT_HANDSHAKE_START;
+ tls->ctx->random_bytes(tls->client_random, sizeof(tls->client_random));
+ tls->ctx->random_bytes(tls->client.legacy_session_id, sizeof(tls->client.legacy_session_id));
+ } else {
+ tls->state = PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO;
+ }
+
+ return tls;
+}
+
+void ptls_free(ptls_t *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);
+ if (tls->is_server) {
+ /* nothing to do */
+ } else {
+ if (tls->client.key_share_ctx != NULL)
+ tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, NULL, ptls_iovec_init(NULL, 0));
+ if (tls->client.certificate_verify.cb != NULL)
+ tls->client.certificate_verify.cb(tls->client.certificate_verify.verify_ctx, ptls_iovec_init(NULL, 0),
+ ptls_iovec_init(NULL, 0));
+ }
+ if (tls->early_data != NULL) {
+ ptls_clear_memory(tls->early_data, sizeof(*tls->early_data));
+ free(tls->early_data);
+ }
+ 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_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;
+}
+
+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) {
+ if (server_name_len == 0)
+ server_name_len = strlen(server_name);
+ if ((duped = malloc(server_name_len + 1)) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
+ memcpy(duped, server_name, server_name_len);
+ duped[server_name_len] = '\0';
+ }
+
+ 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) {
+ if (protocol_len == 0)
+ protocol_len = strlen(protocol);
+ if ((duped = malloc(protocol_len + 1)) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
+ memcpy(duped, protocol, protocol_len);
+ duped[protocol_len] = '\0';
+ }
+
+ 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;
+}
+
+void **ptls_get_data_ptr(ptls_t *tls)
+{
+ return &tls->data_ptr;
+}
+
+static int handle_handshake_message(ptls_t *tls, ptls_buffer_t *sendbuf, 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, sendbuf, 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:
+ if (type == PTLS_HANDSHAKE_TYPE_CERTIFICATE) {
+ ret = client_handle_certificate(tls, message);
+ } else {
+ ret = PTLS_ALERT_UNEXPECTED_MESSAGE;
+ }
+ 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, sendbuf, message);
+ } else {
+ ret = PTLS_ALERT_UNEXPECTED_MESSAGE;
+ }
+ break;
+ 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, sendbuf, message, properties);
+ } else {
+ ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+ }
+ break;
+ case PTLS_STATE_SERVER_EXPECT_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_CLIENT_POST_HANDSHAKE:
+ switch (type) {
+ case PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET:
+ ret = client_handle_new_session_ticket(tls, message);
+ break;
+ default:
+ ret = PTLS_ALERT_UNEXPECTED_MESSAGE;
+ break;
+ }
+ break;
+ case PTLS_STATE_SERVER_POST_HANDSHAKE:
+ ret = PTLS_ALERT_UNEXPECTED_MESSAGE;
+ break;
+ default:
+ assert(!"unexpected state");
+ break;
+ }
+
+ 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 level = src[0], desc = src[1];
+
+ /* ignore certain warnings */
+ if (level == PTLS_ALERT_LEVEL_WARNING) {
+ switch (desc) {
+ case PTLS_ALERT_USER_CANCELED:
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ /* all other alerts are considered fatal, regardless of the transmitted level (section 6) */
+ return PTLS_ALERT_TO_PEER_ERROR(desc);
+}
+
+static int handle_handshake_record(ptls_t *tls, int (*cb)(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t message,
+ int is_end_of_record, ptls_handshake_properties_t *properties),
+ ptls_buffer_t *sendbuf, 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 ((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, sendbuf, ptls_iovec_init(src, mess_len), src_end - src == mess_len, properties);
+ switch (ret) {
+ case 0:
+ 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) {
+ if (tls->recvbuf.mess.base == NULL) {
+ ptls_buffer_init(&tls->recvbuf.mess, "", 0);
+ if ((ret = ptls_buffer_reserve(&tls->recvbuf.mess, src_end - src)) != 0)
+ return ret;
+ memcpy(tls->recvbuf.mess.base, src, src_end - src);
+ } else {
+ memmove(tls->recvbuf.mess.base, src, src_end - src);
+ }
+ tls->recvbuf.mess.off = src_end - src;
+ ret = PTLS_ERROR_IN_PROGRESS;
+ } else {
+ ptls_buffer_dispose(&tls->recvbuf.mess);
+ }
+
+ return ret;
+}
+
+static int handle_input(ptls_t *tls, ptls_buffer_t *sendbuf, 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) {
+ 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, &rec.length, rec.fragment,
+ rec.length)) != 0) {
+ if (tls->skip_early_data) {
+ ret = PTLS_ERROR_IN_PROGRESS;
+ goto NextRecord;
+ }
+ return ret;
+ }
+ 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->skip_early_data) {
+ ret = PTLS_ERROR_IN_PROGRESS;
+ goto NextRecord;
+ }
+
+ if (tls->recvbuf.mess.base != NULL || rec.type == PTLS_CONTENT_TYPE_HANDSHAKE) {
+ /* handshake record */
+ ret = handle_handshake_record(tls, handle_handshake_message, sendbuf, &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;
+}
+
+int ptls_handshake(ptls_t *tls, ptls_buffer_t *sendbuf, const void *input, size_t *inlen, ptls_handshake_properties_t *properties)
+{
+ size_t sendbuf_orig_off = sendbuf->off;
+ int ret;
+
+ assert(tls->state < PTLS_STATE_POST_HANDSHAKE_MIN);
+
+ /* 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, sendbuf, properties, NULL);
+ default:
+ break;
+ }
+
+ const uint8_t *src = input, *const src_end = src + *inlen;
+ ptls_buffer_t decryptbuf;
+ uint8_t decryptbuf_small[256];
+
+ ptls_buffer_init(&decryptbuf, decryptbuf_small, sizeof(decryptbuf_small));
+
+ /* 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, sendbuf, &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:
+ break;
+ default:
+ /* flush partially written response */
+ ptls_clear_memory(sendbuf->base + sendbuf_orig_off, sendbuf->off - sendbuf_orig_off);
+ sendbuf->off = sendbuf_orig_off;
+ /* send alert immediately */
+ if (PTLS_ERROR_GET_CLASS(ret) != PTLS_ERROR_CLASS_PEER_ALERT)
+ if (ptls_send_alert(tls, sendbuf, PTLS_ALERT_LEVEL_FATAL,
+ PTLS_ERROR_GET_CLASS(ret) == PTLS_ERROR_CLASS_SELF_ALERT ? ret : PTLS_ALERT_INTERNAL_ERROR) != 0)
+ sendbuf->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;
+ 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;
+}
+
+int ptls_send(ptls_t *tls, ptls_buffer_t *sendbuf, const void *input, size_t inlen)
+{
+ assert(tls->traffic_protection.enc.aead != NULL);
+ return buffer_push_encrypted_records(sendbuf, PTLS_CONTENT_TYPE_APPDATA, input, inlen, &tls->traffic_protection.enc);
+}
+
+size_t ptls_get_record_overhead(ptls_t *tls)
+{
+ return 6 + tls->traffic_protection.enc.aead->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;
+ ptls_hash_context_t *hctx;
+ 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)
+ return PTLS_ERROR_IN_PROGRESS;
+
+ if ((hctx = algo->create()) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
+ hctx->update(hctx, context_value.base, context_value.len);
+ hctx->final(hctx, context_value_hash, PTLS_HASH_FINAL_MODE_FREE);
+
+ 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;
+ }
+}
+
+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;
+
+ 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 *base_label)
+{
+ ptls_buffer_t hkdf_label;
+ uint8_t hkdf_label_buf[512];
+ 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 (base_label == NULL)
+ base_label = "tls13 ";
+ ptls_buffer_pushv(&hkdf_label, base_label, strlen(base_label));
+ 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;
+}
+
+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 *ptls_aead_new(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret,
+ const char *base_label)
+{
+ ptls_aead_context_t *ctx;
+ uint8_t key[PTLS_MAX_SECRET_SIZE];
+ int ret;
+
+ if ((ctx = (ptls_aead_context_t *)malloc(aead->context_size)) == NULL)
+ return NULL;
+
+ *ctx = (ptls_aead_context_t){aead};
+ if ((ret = get_traffic_key(hash, key, aead->key_size, 0, secret, base_label)) != 0)
+ goto Exit;
+ if ((ret = get_traffic_key(hash, ctx->static_iv, aead->iv_size, 1, secret, base_label)) != 0)
+ goto Exit;
+ ret = aead->setup_crypto(ctx, is_enc, key);
+
+Exit:
+ ptls_clear_memory(key, aead->key_size);
+ if (ret != 0) {
+ ptls_clear_memory(ctx->static_iv, aead->iv_size);
+ free(ctx);
+ ctx = NULL;
+ }
+
+ return ctx;
+}
+
+void ptls_aead_free(ptls_aead_context_t *ctx)
+{
+ ctx->dispose_crypto(ctx);
+ ptls_clear_memory(ctx->static_iv, ctx->algo->iv_size);
+ free(ctx);
+}
+
+size_t ptls_aead_encrypt(ptls_aead_context_t *ctx, void *output, const void *input, size_t inlen, uint64_t seq, const void *aad,
+ size_t aadlen)
+{
+ size_t off = 0;
+
+ ptls_aead_encrypt_init(ctx, seq, aad, aadlen);
+ off += ptls_aead_encrypt_update(ctx, ((uint8_t *)output) + off, input, inlen);
+ off += ptls_aead_encrypt_final(ctx, ((uint8_t *)output) + off);
+
+ return off;
+}
+
+void ptls_aead__build_iv(ptls_aead_context_t *ctx, uint8_t *iv, uint64_t seq)
+{
+ size_t iv_size = ctx->algo->iv_size, i;
+ const uint8_t *s = ctx->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 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};
+