blob: 14ea227daad09454f6dfad281d81a98e97258fca [file] [log] [blame]
/*
* Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifdef _WINDOWS
#include "wincompat.h"
#endif
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WINDOWS
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#endif
#include "picotls.h"
#if PICOTLS_USE_DTRACE
#include "picotls-probes.h"
#endif
#define PTLS_MAX_PLAINTEXT_RECORD_SIZE 16384
#define PTLS_MAX_ENCRYPTED_RECORD_SIZE (16384 + 256)
#define PTLS_RECORD_VERSION_MAJOR 3
#define PTLS_RECORD_VERSION_MINOR 3
#define PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC 20
#define PTLS_CONTENT_TYPE_ALERT 21
#define PTLS_CONTENT_TYPE_HANDSHAKE 22
#define PTLS_CONTENT_TYPE_APPDATA 23
#define PTLS_PSK_KE_MODE_PSK 0
#define PTLS_PSK_KE_MODE_PSK_DHE 1
#define PTLS_HANDSHAKE_HEADER_SIZE 4
#define PTLS_EXTENSION_TYPE_SERVER_NAME 0
#define PTLS_EXTENSION_TYPE_STATUS_REQUEST 5
#define PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS 10
#define PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS 13
#define PTLS_EXTENSION_TYPE_ALPN 16
#define PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE 20
#define PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE 27
#define PTLS_EXTENSION_TYPE_PRE_SHARED_KEY 41
#define PTLS_EXTENSION_TYPE_EARLY_DATA 42
#define PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS 43
#define PTLS_EXTENSION_TYPE_COOKIE 44
#define PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES 45
#define PTLS_EXTENSION_TYPE_KEY_SHARE 51
#define PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS 0xfd00
#define PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO 0xfe0d
#define PTLS_SERVER_NAME_TYPE_HOSTNAME 0
#define PTLS_ECH_CONFIG_VERSION 0xfe0d
#define PTLS_ECH_CLIENT_HELLO_TYPE_OUTER 0
#define PTLS_ECH_CLIENT_HELLO_TYPE_INNER 1
#define PTLS_ECH_CONFIRM_LENGTH 8
static const char ech_info_prefix[8] = "tls ech";
#define PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, server CertificateVerify"
#define PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, client CertificateVerify"
#define PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE \
(64 + sizeof(PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING) + PTLS_MAX_DIGEST_SIZE * 2)
#define PTLS_EARLY_DATA_MAX_DELAY 10000 /* max. RTT (in msec) to permit early data */
#ifndef PTLS_MAX_EARLY_DATA_SKIP_SIZE
#define PTLS_MAX_EARLY_DATA_SKIP_SIZE 65536
#endif
#if defined(PTLS_DEBUG) && PTLS_DEBUG
#define PTLS_DEBUGF(...) fprintf(stderr, __VA_ARGS__)
#else
#define PTLS_DEBUGF(...)
#endif
#ifndef PTLS_MEMORY_DEBUG
#define PTLS_MEMORY_DEBUG 0
#endif
#if PICOTLS_USE_DTRACE
#define PTLS_SHOULD_PROBE(LABEL, tls) (PTLS_UNLIKELY(PICOTLS_##LABEL##_ENABLED()) && !(tls)->skip_tracing)
#define PTLS_PROBE0(LABEL, tls) \
do { \
ptls_t *_tls = (tls); \
if (PTLS_SHOULD_PROBE(LABEL, _tls)) \
PICOTLS_##LABEL(_tls); \
} while (0)
#define PTLS_PROBE(LABEL, tls, ...) \
do { \
ptls_t *_tls = (tls); \
if (PTLS_SHOULD_PROBE(LABEL, _tls)) \
PICOTLS_##LABEL(_tls, __VA_ARGS__); \
} while (0)
#else
#define PTLS_PROBE0(LABEL, tls)
#define PTLS_PROBE(LABEL, tls, ...)
#endif
/**
* list of supported versions in the preferred order
*/
static const uint16_t supported_versions[] = {PTLS_PROTOCOL_VERSION_TLS13};
static const uint8_t hello_retry_random[PTLS_HELLO_RANDOM_SIZE] = {0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C,
0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB,
0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C};
struct st_ptls_traffic_protection_t {
uint8_t secret[PTLS_MAX_DIGEST_SIZE];
size_t epoch;
/* the following fields are not used if the key_change callback is set */
ptls_aead_context_t *aead;
uint64_t seq;
unsigned tls12 : 1;
uint64_t tls12_enc_record_iv;
};
struct st_ptls_record_message_emitter_t {
ptls_message_emitter_t super;
size_t rec_start;
};
struct st_ptls_signature_algorithms_t {
uint16_t list[16]; /* expand? */
size_t count;
};
struct st_ptls_certificate_request_t {
/**
* context.base becomes non-NULL when a CertificateRequest is pending for processing
*/
ptls_iovec_t context;
struct st_ptls_signature_algorithms_t signature_algorithms;
};
struct st_decoded_ech_config_t {
uint8_t id;
ptls_hpke_kem_t *kem;
ptls_iovec_t public_key;
ptls_hpke_cipher_suite_t *cipher;
uint8_t max_name_length;
ptls_iovec_t public_name;
ptls_iovec_t bytes;
};
/**
* Properties for ECH. Iff ECH is used and not rejected, `aead` is non-NULL.
*/
struct st_ptls_ech_t {
uint8_t offered : 1;
uint8_t offered_grease : 1;
uint8_t accepted : 1;
uint8_t config_id;
ptls_hpke_kem_t *kem;
ptls_hpke_cipher_suite_t *cipher;
ptls_aead_context_t *aead;
uint8_t inner_client_random[PTLS_HELLO_RANDOM_SIZE];
struct {
ptls_iovec_t enc;
uint8_t max_name_length;
char *public_name;
/**
* retains a copy of entire ECH extension so that it can be replayed in the 2nd CH when ECH is rejected via HRR
*/
ptls_iovec_t first_ech;
} client;
};
struct st_ptls_t {
/**
* the context
*/
ptls_context_t *ctx;
/**
* the state
*/
enum en_ptls_state_t {
PTLS_STATE_CLIENT_HANDSHAKE_START,
PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO,
PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO,
PTLS_STATE_CLIENT_EXPECT_ENCRYPTED_EXTENSIONS,
PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE,
PTLS_STATE_CLIENT_EXPECT_CERTIFICATE,
PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY,
PTLS_STATE_CLIENT_EXPECT_FINISHED,
PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO,
PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO,
PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY,
PTLS_STATE_SERVER_EXPECT_CERTIFICATE,
PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY,
/* ptls_send can be called if the state is below here */
PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA,
PTLS_STATE_SERVER_EXPECT_FINISHED,
PTLS_STATE_POST_HANDSHAKE_MIN,
PTLS_STATE_CLIENT_POST_HANDSHAKE = PTLS_STATE_POST_HANDSHAKE_MIN,
PTLS_STATE_SERVER_POST_HANDSHAKE
} state;
/**
* receive buffers
*/
struct {
ptls_buffer_t rec;
ptls_buffer_t mess;
} recvbuf;
/**
* key schedule
*/
ptls_key_schedule_t *key_schedule;
/**
* values used for record protection
*/
struct {
struct st_ptls_traffic_protection_t dec;
struct st_ptls_traffic_protection_t enc;
} traffic_protection;
/**
* server-name passed using SNI
*/
char *server_name;
/**
* result of ALPN
*/
char *negotiated_protocol;
/**
* selected key-exchange
*/
ptls_key_exchange_algorithm_t *key_share;
/**
* selected cipher-suite
*/
ptls_cipher_suite_t *cipher_suite;
/**
* ClientHello.random that appears on the wire. When ECH is used, that of inner CH is retained separately.
*/
uint8_t client_random[PTLS_HELLO_RANDOM_SIZE];
/**
* exporter master secret (either 0rtt or 1rtt)
*/
struct {
uint8_t *early;
uint8_t *one_rtt;
} exporter_master_secret;
/**
* ECH
*/
struct st_ptls_ech_t ech;
/* flags */
unsigned is_server : 1;
unsigned is_psk_handshake : 1;
unsigned send_change_cipher_spec : 1;
unsigned needs_key_update : 1;
unsigned key_update_send_request : 1;
unsigned skip_tracing : 1;
/**
* misc.
*/
union {
struct {
ptls_iovec_t legacy_session_id;
uint8_t legacy_session_id_buf[32];
ptls_key_exchange_context_t *key_share_ctx;
unsigned offered_psk : 1;
/**
* if 1-RTT write key is active
*/
unsigned using_early_data : 1;
struct st_ptls_certificate_request_t certificate_request;
} client;
struct {
uint8_t pending_traffic_secret[PTLS_MAX_DIGEST_SIZE];
uint32_t early_data_skipped_bytes; /* if not UINT32_MAX, the server is skipping early data */
unsigned can_send_session_ticket : 1;
ptls_async_job_t *async_job;
} server;
};
/**
* certificate verify; will be used by the client and the server (if require_client_authentication is set)
*/
struct {
int (*cb)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature);
void *verify_ctx;
} certificate_verify;
/**
* handshake traffic secret to be commisioned (an array of `uint8_t [PTLS_MAX_DIGEST_SIZE]` or NULL)
*/
uint8_t *pending_handshake_secret;
/**
* user data
*/
void *data_ptr;
};
struct st_ptls_record_t {
uint8_t type;
uint16_t version;
size_t length;
const uint8_t *fragment;
};
struct st_ptls_client_hello_psk_t {
ptls_iovec_t identity;
uint32_t obfuscated_ticket_age;
ptls_iovec_t binder;
};
#define MAX_UNKNOWN_EXTENSIONS 16
#define MAX_CERTIFICATE_TYPES 8
struct st_ptls_client_hello_t {
uint16_t legacy_version;
const uint8_t *random_bytes;
ptls_iovec_t legacy_session_id;
struct {
const uint8_t *ids;
size_t count;
} compression_methods;
uint16_t selected_version;
ptls_iovec_t cipher_suites;
ptls_iovec_t negotiated_groups;
ptls_iovec_t key_shares;
struct st_ptls_signature_algorithms_t signature_algorithms;
ptls_iovec_t server_name;
struct {
ptls_iovec_t list[16];
size_t count;
} alpn;
struct {
uint16_t list[16];
size_t count;
} cert_compression_algos;
struct {
ptls_iovec_t all;
ptls_iovec_t tbs;
ptls_iovec_t ch1_hash;
ptls_iovec_t signature;
unsigned sent_key_share : 1;
} cookie;
struct {
uint8_t list[MAX_CERTIFICATE_TYPES];
size_t count;
} server_certificate_types;
unsigned status_request : 1;
/**
* ECH: payload.base != NULL indicates that the extension was received
*/
struct {
uint8_t type;
uint8_t config_id;
ptls_hpke_cipher_suite_id_t cipher_suite;
ptls_iovec_t enc;
ptls_iovec_t payload;
} ech;
struct {
const uint8_t *hash_end;
struct {
struct st_ptls_client_hello_psk_t list[4];
size_t count;
} identities;
unsigned ke_modes;
unsigned early_data_indication : 1;
unsigned is_last_extension : 1;
} psk;
ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
size_t first_extension_at;
};
struct st_ptls_server_hello_t {
uint8_t random_[PTLS_HELLO_RANDOM_SIZE];
ptls_iovec_t legacy_session_id;
int is_retry_request;
union {
ptls_iovec_t peerkey;
struct {
uint16_t selected_group;
ptls_iovec_t cookie;
const uint8_t *ech;
} retry_request;
};
};
struct st_ptls_key_schedule_t {
unsigned generation; /* early secret (1), hanshake secret (2), master secret (3) */
uint8_t secret[PTLS_MAX_DIGEST_SIZE];
size_t num_hashes;
struct {
ptls_hash_algorithm_t *algo;
ptls_hash_context_t *ctx, *ctx_outer;
} hashes[1];
};
struct st_ptls_extension_decoder_t {
uint16_t type;
int (*cb)(ptls_t *tls, void *arg, const uint8_t *src, const uint8_t *const end);
};
struct st_ptls_extension_bitmap_t {
uint64_t bits;
};
static const uint8_t zeroes_of_max_digest_size[PTLS_MAX_DIGEST_SIZE] = {0};
static ptls_aead_context_t *new_aead(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret,
ptls_iovec_t hash_value, const char *label_prefix);
static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify,
struct st_ptls_signature_algorithms_t *signature_algorithms);
static int is_supported_version(uint16_t v)
{
size_t i;
for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i)
if (supported_versions[i] == v)
return 1;
return 0;
}
static int extension_bitmap_testandset(struct st_ptls_extension_bitmap_t *bitmap, int hstype, uint16_t extid)
{
#define HSTYPE_TO_BIT(hstype) ((uint64_t)1 << ((hstype) + 1)) /* min(hstype) is -1 (PSEUDO_HRR) */
#define DEFINE_BIT(abbrev, hstype) static const uint64_t abbrev = HSTYPE_TO_BIT(PTLS_HANDSHAKE_TYPE_##hstype)
#define EXT(candext, allowed_bits) \
do { \
if (PTLS_UNLIKELY(extid == PTLS_EXTENSION_TYPE_##candext)) { \
allowed_hs_bits = allowed_bits; \
goto Found; \
} \
ext_bitmap_mask <<= 1; \
} while (0)
DEFINE_BIT(CH, CLIENT_HELLO);
DEFINE_BIT(SH, SERVER_HELLO);
DEFINE_BIT(HRR, PSEUDO_HRR);
DEFINE_BIT(EE, ENCRYPTED_EXTENSIONS);
DEFINE_BIT(CR, CERTIFICATE_REQUEST);
DEFINE_BIT(CT, CERTIFICATE);
DEFINE_BIT(NST, NEW_SESSION_TICKET);
uint64_t allowed_hs_bits, ext_bitmap_mask = 1;
/* clang-format off */
/* RFC 8446 section 4.2: "The table below indicates the messages where a given extension may appear... If an implementation
* receives an extension which it recognizes and which is not specified for the message in which it appears, it MUST abort the
* handshake with an "illegal_parameter" alert.
*
* +-------------------------+---------------+
* + Extension | Allowed |
* +-------------------------+---------------+ */
EXT( SERVER_NAME , CH + EE );
EXT( STATUS_REQUEST , CH + CR + CT );
EXT( SUPPORTED_GROUPS , CH + EE );
EXT( SIGNATURE_ALGORITHMS , CH + CR );
EXT( ALPN , CH + EE );
EXT( SERVER_CERTIFICATE_TYPE , CH + EE );
EXT( KEY_SHARE , CH + SH + HRR );
EXT( PRE_SHARED_KEY , CH + SH );
EXT( PSK_KEY_EXCHANGE_MODES , CH );
EXT( EARLY_DATA , CH + EE + NST );
EXT( COOKIE , CH + HRR );
EXT( SUPPORTED_VERSIONS , CH + SH + HRR );
EXT( COMPRESS_CERTIFICATE , CH + CR ); /* from RFC 8879 */
EXT( ENCRYPTED_CLIENT_HELLO , CH + HRR + EE ); /* from draft-ietf-tls-esni-15 */
EXT( ECH_OUTER_EXTENSIONS , 0 );
/* +-----------------------------------------+ */
/* clang-format on */
return 1;
Found:
if ((allowed_hs_bits & HSTYPE_TO_BIT(hstype)) == 0)
return 0;
if ((bitmap->bits & ext_bitmap_mask) != 0)
return 0;
bitmap->bits |= ext_bitmap_mask;
return 1;
#undef HSTYPE_TO_BIT
#undef DEFINE_ABBREV
#undef EXT
}
#ifndef ntoh16
static uint16_t ntoh16(const uint8_t *src)
{
return (uint16_t)src[0] << 8 | src[1];
}
#endif
#ifndef ntoh24
static uint32_t ntoh24(const uint8_t *src)
{
return (uint32_t)src[0] << 16 | (uint32_t)src[1] << 8 | src[2];
}
#endif
#ifndef ntoh32
static uint32_t ntoh32(const uint8_t *src)
{
return (uint32_t)src[0] << 24 | (uint32_t)src[1] << 16 | (uint32_t)src[2] << 8 | src[3];
}
#endif
#ifndef ntoh64
static uint64_t ntoh64(const uint8_t *src)
{
return (uint64_t)src[0] << 56 | (uint64_t)src[1] << 48 | (uint64_t)src[2] << 40 | (uint64_t)src[3] << 32 |
(uint64_t)src[4] << 24 | (uint64_t)src[5] << 16 | (uint64_t)src[6] << 8 | src[7];
}
#endif
static void encode64(uint8_t *dst, uint64_t v)
{
for (size_t i = 0; i < 8; ++i)
dst[i] = (uint8_t)(v >> (56 - 8 * i));
}
static char *duplicate_as_str(const void *src, size_t len)
{
char *dst;
if ((dst = malloc(len + 1)) == NULL)
return NULL;
memcpy(dst, src, len);
dst[len] = '\0';
return dst;
}
void ptls_buffer__release_memory(ptls_buffer_t *buf)
{
ptls_clear_memory(buf->base, buf->off);
if (buf->is_allocated) {
#ifdef _WINDOWS
if (buf->align_bits != 0) {
_aligned_free(buf->base);
} else {
free(buf->base);
}
#else
free(buf->base);
#endif
}
}
int ptls_buffer_reserve(ptls_buffer_t *buf, size_t delta)
{
return ptls_buffer_reserve_aligned(buf, delta, 0);
}
int ptls_buffer_reserve_aligned(ptls_buffer_t *buf, size_t delta, uint8_t align_bits)
{
if (buf->base == NULL)
return PTLS_ERROR_NO_MEMORY;
if (PTLS_MEMORY_DEBUG || buf->capacity < buf->off + delta ||
(buf->align_bits < align_bits && ((uintptr_t)buf->base & (((uintptr_t)1 << align_bits) - 1)) != 0)) {
void *newp;
size_t new_capacity = buf->capacity;
if (new_capacity < 1024)
new_capacity = 1024;
while (new_capacity < buf->off + delta) {
new_capacity *= 2;
}
if (align_bits != 0) {
#ifdef _WINDOWS
if ((newp = _aligned_malloc(new_capacity, (size_t)1 << align_bits)) == NULL)
return PTLS_ERROR_NO_MEMORY;
#else
if (posix_memalign(&newp, 1 << align_bits, new_capacity) != 0)
return PTLS_ERROR_NO_MEMORY;
#endif
} else {
if ((newp = malloc(new_capacity)) == NULL)
return PTLS_ERROR_NO_MEMORY;
}
memcpy(newp, buf->base, buf->off);
ptls_buffer__release_memory(buf);
buf->base = newp;
buf->capacity = new_capacity;
buf->is_allocated = 1;
buf->align_bits = align_bits;
}
return 0;
}
int ptls_buffer__do_pushv(ptls_buffer_t *buf, const void *src, size_t len)
{
int ret;
if (len == 0)
return 0;
if ((ret = ptls_buffer_reserve(buf, len)) != 0)
return ret;
memcpy(buf->base + buf->off, src, len);
buf->off += len;
return 0;
}
int ptls_buffer__adjust_quic_blocksize(ptls_buffer_t *buf, size_t body_size)
{
uint8_t sizebuf[PTLS_ENCODE_QUICINT_CAPACITY];
size_t sizelen = ptls_encode_quicint(sizebuf, body_size) - sizebuf;
/* adjust amount of space before body_size to `sizelen` bytes */
if (sizelen != 1) {
int ret;
if ((ret = ptls_buffer_reserve(buf, sizelen - 1)) != 0)
return ret;
memmove(buf->base + buf->off - body_size - 1 + sizelen, buf->base + buf->off - body_size, body_size);
buf->off += sizelen - 1;
}
/* write the size */
memcpy(buf->base + buf->off - body_size - sizelen, sizebuf, sizelen);
return 0;
}
int ptls_buffer__adjust_asn1_blocksize(ptls_buffer_t *buf, size_t body_size)
{
fprintf(stderr, "unimplemented\n");
abort();
}
int ptls_buffer_push_asn1_ubigint(ptls_buffer_t *buf, const void *bignum, size_t size)
{
const uint8_t *p = bignum, *const end = p + size;
int ret;
/* skip zeroes */
for (; end - p >= 1; ++p)
if (*p != 0)
break;
/* emit */
ptls_buffer_push(buf, 2);
ptls_buffer_push_asn1_block(buf, {
if (*p >= 0x80)
ptls_buffer_push(buf, 0);
if (p != end) {
ptls_buffer_pushv(buf, p, end - p);
} else {
ptls_buffer_pushv(buf, "", 1);
}
});
ret = 0;
Exit:
return ret;
}
#if PTLS_FUZZ_HANDSHAKE
static size_t aead_encrypt(struct st_ptls_traffic_protection_t *ctx, void *output, const void *input, size_t inlen,
uint8_t content_type)
{
memcpy(output, input, inlen);
memcpy(output + inlen, &content_type, 1);
return inlen + 1 + 16;
}
static int aead_decrypt(struct st_ptls_traffic_protection_t *ctx, void *output, size_t *outlen, const void *input, size_t inlen)
{
if (inlen < 16) {
return PTLS_ALERT_BAD_RECORD_MAC;
}
memcpy(output, input, inlen - 16);
*outlen = inlen - 16; /* removing the 16 bytes of tag */
return 0;
}
#else
static void build_aad(uint8_t aad[5], size_t reclen)
{
aad[0] = PTLS_CONTENT_TYPE_APPDATA;
aad[1] = PTLS_RECORD_VERSION_MAJOR;
aad[2] = PTLS_RECORD_VERSION_MINOR;
aad[3] = (uint8_t)(reclen >> 8);
aad[4] = (uint8_t)reclen;
}
static size_t aead_encrypt(struct st_ptls_traffic_protection_t *ctx, void *output, const void *input, size_t inlen,
uint8_t content_type)
{
ptls_iovec_t invec[2] = {ptls_iovec_init(input, inlen), ptls_iovec_init(&content_type, 1)};
uint8_t aad[5];
build_aad(aad, inlen + 1 + ctx->aead->algo->tag_size);
ptls_aead_encrypt_v(ctx->aead, output, invec, PTLS_ELEMENTSOF(invec), ctx->seq++, aad, sizeof(aad));
return inlen + 1 + ctx->aead->algo->tag_size;
}
static int aead_decrypt(struct st_ptls_traffic_protection_t *ctx, void *output, size_t *outlen, const void *input, size_t inlen)
{
uint8_t aad[5];
build_aad(aad, inlen);
if ((*outlen = ptls_aead_decrypt(ctx->aead, output, input, inlen, ctx->seq, aad, sizeof(aad))) == SIZE_MAX)
return PTLS_ALERT_BAD_RECORD_MAC;
++ctx->seq;
return 0;
}
#endif /* #if PTLS_FUZZ_HANDSHAKE */
static void build_tls12_aad(uint8_t *aad, uint8_t type, uint64_t seq, uint16_t length)
{
for (size_t i = 0; i < 8; ++i)
aad[i] = (uint8_t)(seq >> (56 - i * 8));
aad[8] = type;
aad[9] = PTLS_RECORD_VERSION_MAJOR;
aad[10] = PTLS_RECORD_VERSION_MINOR;
aad[11] = length >> 8;
aad[12] = (uint8_t)length;
}
#define buffer_push_record(buf, type, block) \
do { \
ptls_buffer_push((buf), (type), PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR); \
ptls_buffer_push_block((buf), 2, block); \
} while (0)
static int buffer_push_encrypted_records(ptls_buffer_t *buf, uint8_t type, const uint8_t *src, size_t len,
struct st_ptls_traffic_protection_t *enc)
{
int ret = 0;
while (len != 0) {
size_t chunk_size = len;
if (chunk_size > PTLS_MAX_PLAINTEXT_RECORD_SIZE)
chunk_size = PTLS_MAX_PLAINTEXT_RECORD_SIZE;
if (enc->tls12) {
buffer_push_record(buf, type, {
/* reserve memory */
if ((ret = ptls_buffer_reserve_aligned(
buf, enc->aead->algo->tls12.record_iv_size + chunk_size + enc->aead->algo->tag_size,
enc->aead->algo->align_bits)) != 0)
goto Exit;
/* determine nonce, as well as prepending that walue as the record IV (AES-GCM) */
uint64_t nonce;
if (enc->aead->algo->tls12.record_iv_size != 0) {
assert(enc->aead->algo->tls12.record_iv_size == 8);
nonce = enc->tls12_enc_record_iv++;
encode64(buf->base + buf->off, nonce);
buf->off += 8;
} else {
nonce = enc->seq;
}
/* build AAD */
uint8_t aad[PTLS_TLS12_AAD_SIZE];
build_tls12_aad(aad, type, enc->seq, (uint16_t)chunk_size);
/* encrypt */
buf->off += ptls_aead_encrypt(enc->aead, buf->base + buf->off, src, chunk_size, nonce, aad, sizeof(aad));
++enc->seq;
});
} else {
buffer_push_record(buf, PTLS_CONTENT_TYPE_APPDATA, {
if ((ret = ptls_buffer_reserve_aligned(buf, chunk_size + enc->aead->algo->tag_size + 1,
enc->aead->algo->align_bits)) != 0)
goto Exit;
buf->off += aead_encrypt(enc, buf->base + buf->off, src, chunk_size, type);
});
}
src += chunk_size;
len -= chunk_size;
}
Exit:
return ret;
}
static int buffer_encrypt_record(ptls_buffer_t *buf, size_t rec_start, struct st_ptls_traffic_protection_t *enc)
{
size_t bodylen = buf->off - rec_start - 5;
uint8_t *tmpbuf, type = buf->base[rec_start];
int ret;
/* Fast path: do in-place encryption if only one record needs to be emitted. (For simplicity, do not take this path if TLS 1.2
* is used, as this function will be called no more than once per connection, for encrypting an alert.) */
if (!enc->tls12 && bodylen <= PTLS_MAX_PLAINTEXT_RECORD_SIZE) {
size_t overhead = 1 + enc->aead->algo->tag_size;
if ((ret = ptls_buffer_reserve_aligned(buf, overhead, enc->aead->algo->align_bits)) != 0)
return ret;
size_t encrypted_len = aead_encrypt(enc, buf->base + rec_start + 5, buf->base + rec_start + 5, bodylen, type);
assert(encrypted_len == bodylen + overhead);
buf->off += overhead;
buf->base[rec_start] = PTLS_CONTENT_TYPE_APPDATA;
buf->base[rec_start + 3] = (encrypted_len >> 8) & 0xff;
buf->base[rec_start + 4] = encrypted_len & 0xff;
return 0;
}
/* move plaintext to temporary buffer */
if ((tmpbuf = malloc(bodylen)) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
memcpy(tmpbuf, buf->base + rec_start + 5, bodylen);
ptls_clear_memory(buf->base + rec_start, bodylen + 5);
buf->off = rec_start;
/* push encrypted records */
ret = buffer_push_encrypted_records(buf, type, tmpbuf, bodylen, enc);
Exit:
if (tmpbuf != NULL) {
ptls_clear_memory(tmpbuf, bodylen);
free(tmpbuf);
}
return ret;
}
static int begin_record_message(ptls_message_emitter_t *_self)
{
struct st_ptls_record_message_emitter_t *self = (void *)_self;
int ret;
self->rec_start = self->super.buf->off;
ptls_buffer_push(self->super.buf, PTLS_CONTENT_TYPE_HANDSHAKE, PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR, 0, 0);
ret = 0;
Exit:
return ret;
}
static int commit_record_message(ptls_message_emitter_t *_self)
{
struct st_ptls_record_message_emitter_t *self = (void *)_self;
int ret;
if (self->super.enc->aead != NULL) {
ret = buffer_encrypt_record(self->super.buf, self->rec_start, self->super.enc);
} else {
/* TODO allow CH,SH,HRR above 16KB */
size_t sz = self->super.buf->off - self->rec_start - 5;
assert(sz <= PTLS_MAX_PLAINTEXT_RECORD_SIZE);
self->super.buf->base[self->rec_start + 3] = (uint8_t)(sz >> 8);
self->super.buf->base[self->rec_start + 4] = (uint8_t)(sz);
ret = 0;
}
return ret;
}
#define buffer_push_extension(buf, type, block) \
do { \
ptls_buffer_push16((buf), (type)); \
ptls_buffer_push_block((buf), 2, block); \
} while (0);
#define decode_open_extensions(src, end, hstype, exttype, block) \
do { \
struct st_ptls_extension_bitmap_t bitmap = {0}; \
ptls_decode_open_block((src), end, 2, { \
while ((src) != end) { \
if ((ret = ptls_decode16((exttype), &(src), end)) != 0) \
goto Exit; \
if (!extension_bitmap_testandset(&bitmap, (hstype), *(exttype))) { \
ret = PTLS_ALERT_ILLEGAL_PARAMETER; \
goto Exit; \
} \
ptls_decode_open_block((src), end, 2, block); \
} \
}); \
} while (0)
#define decode_extensions(src, end, hstype, exttype, block) \
do { \
decode_open_extensions((src), end, hstype, exttype, block); \
ptls_decode_assert_block_close((src), end); \
} while (0)
int ptls_decode8(uint8_t *value, const uint8_t **src, const uint8_t *end)
{
if (*src == end)
return PTLS_ALERT_DECODE_ERROR;
*value = *(*src)++;
return 0;
}
int ptls_decode16(uint16_t *value, const uint8_t **src, const uint8_t *end)
{
if (end - *src < 2)
return PTLS_ALERT_DECODE_ERROR;
*value = ntoh16(*src);
*src += 2;
return 0;
}
int ptls_decode24(uint32_t *value, const uint8_t **src, const uint8_t *end)
{
if (end - *src < 3)
return PTLS_ALERT_DECODE_ERROR;
*value = ((uint32_t)(*src)[0] << 16) | ((uint32_t)(*src)[1] << 8) | (*src)[2];
*src += 3;
return 0;
}
int ptls_decode32(uint32_t *value, const uint8_t **src, const uint8_t *end)
{
if (end - *src < 4)
return PTLS_ALERT_DECODE_ERROR;
*value = ntoh32(*src);
*src += 4;
return 0;
}
int ptls_decode64(uint64_t *value, const uint8_t **src, const uint8_t *end)
{
if (end - *src < 8)
return PTLS_ALERT_DECODE_ERROR;
*value = ntoh64(*src);
*src += 8;
return 0;
}
uint64_t ptls_decode_quicint(const uint8_t **src, const uint8_t *end)
{
if (PTLS_UNLIKELY(*src == end))
return UINT64_MAX;
uint8_t b = *(*src)++;
if (PTLS_LIKELY(b <= 0x3f))
return b;
uint64_t v = b & 0x3f;
unsigned bytes_left = (1 << (b >> 6)) - 1;
if (PTLS_UNLIKELY((size_t)(end - *src) < bytes_left))
return UINT64_MAX;
do {
v = (v << 8) | *(*src)++;
} while (--bytes_left != 0);
return v;
}
static void log_secret(ptls_t *tls, const char *type, ptls_iovec_t secret)
{
char hexbuf[PTLS_MAX_DIGEST_SIZE * 2 + 1];
PTLS_PROBE(NEW_SECRET, tls, type, ptls_hexdump(hexbuf, secret.base, secret.len));
PTLS_LOG_CONN(new_secret, tls, { PTLS_LOG_ELEMENT_SAFESTR(label, type); });
if (tls->ctx->log_event != NULL)
tls->ctx->log_event->cb(tls->ctx->log_event, tls, type, "%s", ptls_hexdump(hexbuf, secret.base, secret.len));
}
/**
* This function preserves the flags and modes (e.g., `offered`, `accepted`, `cipher`), they can be used afterwards.
*/
static void clear_ech(struct st_ptls_ech_t *ech, int is_server)
{
if (ech->aead != NULL) {
ptls_aead_free(ech->aead);
ech->aead = NULL;
}
ptls_clear_memory(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE);
if (!is_server) {
free(ech->client.enc.base);
ech->client.enc = ptls_iovec_init(NULL, 0);
if (ech->client.public_name != NULL) {
free(ech->client.public_name);
ech->client.public_name = NULL;
}
free(ech->client.first_ech.base);
ech->client.first_ech = ptls_iovec_init(NULL, 0);
}
}
/**
* Decodes one ECHConfigContents (tls-esni-15 section 4). `decoded->kem` and `cipher` may be NULL even when the function returns
* zero, if the corresponding entries are not found.
*/
static int decode_one_ech_config(ptls_hpke_kem_t **kems, ptls_hpke_cipher_suite_t **ciphers,
struct st_decoded_ech_config_t *decoded, const uint8_t **src, const uint8_t *const end)
{
char *public_name_buf = NULL;
int ret;
*decoded = (struct st_decoded_ech_config_t){0};
if ((ret = ptls_decode8(&decoded->id, src, end)) != 0)
goto Exit;
uint16_t kem_id;
if ((ret = ptls_decode16(&kem_id, src, end)) != 0)
goto Exit;
for (size_t i = 0; kems[i] != NULL; ++i) {
if (kems[i]->id == kem_id) {
decoded->kem = kems[i];
break;
}
}
ptls_decode_open_block(*src, end, 2, {
if (*src == end) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
decoded->public_key = ptls_iovec_init(*src, end - *src);
*src = end;
});
ptls_decode_open_block(*src, end, 2, {
do {
uint16_t kdf_id;
uint16_t aead_id;
if ((ret = ptls_decode16(&kdf_id, src, end)) != 0)
goto Exit;
if ((ret = ptls_decode16(&aead_id, src, end)) != 0)
goto Exit;
if (decoded->cipher == NULL) {
for (size_t i = 0; ciphers[i] != NULL; ++i) {
if (ciphers[i]->id.kdf == kdf_id && ciphers[i]->id.aead == aead_id) {
decoded->cipher = ciphers[i];
break;
}
}
}
} while (*src != end);
});
if ((ret = ptls_decode8(&decoded->max_name_length, src, end)) != 0)
goto Exit;
#define SKIP_DECODED() \
do { \
decoded->kem = NULL; \
decoded->cipher = NULL; \
} while (0)
/* Decode public_name. The specification requires clients to ignore (upon parsing ESNIConfigList) or reject (upon handshake)
* public names that are not DNS names or IPv4 addresses. We ignore IPv4 and v6 addresses during parsing (IPv6 addresses never
* looks like DNS names), and delegate the responsibility of rejecting non-DNS names to the certificate verify callback. */
ptls_decode_open_block(*src, end, 1, {
if (*src == end) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
if ((public_name_buf = duplicate_as_str(*src, end - *src)) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
if (ptls_server_name_is_ipaddr(public_name_buf)) {
SKIP_DECODED();
} else {
decoded->public_name = ptls_iovec_init(*src, end - *src);
}
*src = end;
});
ptls_decode_block(*src, end, 2, {
while (*src < end) {
uint16_t type;
if ((ret = ptls_decode16(&type, src, end)) != 0)
goto Exit;
ptls_decode_open_block(*src, end, 2, { *src = end; });
/* if a critital extension is found, indicate that the config cannot be used */
if ((type & 0x8000) != 0)
SKIP_DECODED();
}
});
#undef SKIP_DECODED
Exit:
free(public_name_buf);
return ret;
}
static int client_decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list)
{
const uint8_t *src = config_list.base, *const end = src + config_list.len;
int match_found = 0, ret;
*decoded = (struct st_decoded_ech_config_t){0};
ptls_decode_block(src, end, 2, {
do {
const uint8_t *config_start = src;
uint16_t version;
if ((ret = ptls_decode16(&version, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 2, {
/* If the block is the one that we recognize, parse it, then adopt if if possible. Otherwise, skip. */
if (version == PTLS_ECH_CONFIG_VERSION) {
struct st_decoded_ech_config_t thisconf;
if ((ret = decode_one_ech_config(ctx->ech.client.kems, ctx->ech.client.ciphers, &thisconf, &src, end)) != 0)
goto Exit;
if (!match_found && thisconf.kem != NULL && thisconf.cipher != NULL) {
*decoded = thisconf;
decoded->bytes = ptls_iovec_init(config_start, end - config_start);
match_found = 1;
}
} else {
src = end;
}
});
} while (src != end);
});
ret = 0;
Exit:
if (ret != 0)
*decoded = (struct st_decoded_ech_config_t){0};
return ret;
}
static int client_setup_ech(struct st_ptls_ech_t *ech, struct st_decoded_ech_config_t *decoded,
void (*random_bytes)(void *, size_t))
{
ptls_buffer_t infobuf;
uint8_t infobuf_smallbuf[256];
int ret;
/* setup `enc` and `aead` by running HPKE */
ptls_buffer_init(&infobuf, infobuf_smallbuf, sizeof(infobuf_smallbuf));
ptls_buffer_pushv(&infobuf, ech_info_prefix, sizeof(ech_info_prefix));
ptls_buffer_pushv(&infobuf, decoded->bytes.base, decoded->bytes.len);
if ((ret = ptls_hpke_setup_base_s(decoded->kem, decoded->cipher, &ech->client.enc, &ech->aead, decoded->public_key,
ptls_iovec_init(infobuf.base, infobuf.off))) != 0)
goto Exit;
/* setup the rest */
ech->config_id = decoded->id;
ech->kem = decoded->kem;
ech->cipher = decoded->cipher;
random_bytes(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE);
ech->client.max_name_length = decoded->max_name_length;
if ((ech->client.public_name = duplicate_as_str(decoded->public_name.base, decoded->public_name.len)) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
Exit:
if (ret != 0)
clear_ech(ech, 0);
return ret;
}
static void client_setup_ech_grease(struct st_ptls_ech_t *ech, void (*random_bytes)(void *, size_t), ptls_hpke_kem_t **kems,
ptls_hpke_cipher_suite_t **ciphers, const char *sni_name)
{
static const size_t x25519_key_size = 32;
uint8_t random_secret[PTLS_AES128_KEY_SIZE + PTLS_AES_IV_SIZE];
/* pick up X25519, AES-128-GCM or bail out */
for (size_t i = 0; kems[i] != NULL; ++i) {
if (kems[i]->id == PTLS_HPKE_KEM_X25519_SHA256) {
ech->kem = kems[i];
break;
}
}
for (size_t i = 0; ciphers[i] != NULL; ++i) {
if (ciphers[i]->id.kdf == PTLS_HPKE_HKDF_SHA256 && ciphers[i]->id.aead == PTLS_HPKE_AEAD_AES_128_GCM) {
ech->cipher = ciphers[i];
break;
}
}
if (ech->kem == NULL || ech->cipher == NULL)
goto Fail;
/* aead is generated from random */
random_bytes(random_secret, sizeof(random_secret));
ech->aead = ptls_aead_new_direct(ech->cipher->aead, 1, random_secret, random_secret + PTLS_AES128_KEY_SIZE);
/* `enc` is random bytes */
if ((ech->client.enc.base = malloc(x25519_key_size)) == NULL)
goto Fail;
ech->client.enc.len = x25519_key_size;
random_bytes(ech->client.enc.base, ech->client.enc.len);
/* setup the rest (inner_client_random is left zeros) */
random_bytes(&ech->config_id, sizeof(ech->config_id));
ech->client.max_name_length = 64;
if ((ech->client.public_name = duplicate_as_str(sni_name, strlen(sni_name))) == NULL)
goto Fail;
return;
Fail:
clear_ech(ech, 0);
}
#define ECH_CONFIRMATION_SERVER_HELLO "ech accept confirmation"
#define ECH_CONFIRMATION_HRR "hrr ech accept confirmation"
static int ech_calc_confirmation(ptls_key_schedule_t *sched, void *dst, const uint8_t *inner_random, const char *label,
ptls_iovec_t message)
{
ptls_hash_context_t *hash = NULL;
uint8_t secret[PTLS_MAX_DIGEST_SIZE], transcript_hash[PTLS_MAX_DIGEST_SIZE];
int ret;
/* calc transcript hash using the modified ServerHello / HRR */
if ((hash = sched->hashes[0].ctx->clone_(sched->hashes[0].ctx)) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
hash->update(hash, message.base, message.len);
hash->final(hash, transcript_hash, PTLS_HASH_FINAL_MODE_FREE);
hash = NULL;
/* HKDF extract and expand */
if ((ret = ptls_hkdf_extract(sched->hashes[0].algo, secret, ptls_iovec_init(NULL, 0),
ptls_iovec_init(inner_random, PTLS_HELLO_RANDOM_SIZE))) != 0)
goto Exit;
if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, dst, 8, ptls_iovec_init(secret, sched->hashes[0].algo->digest_size),
label, ptls_iovec_init(transcript_hash, sched->hashes[0].algo->digest_size), NULL)) != 0)
goto Exit;
Exit:
ptls_clear_memory(secret, sizeof(secret));
ptls_clear_memory(transcript_hash, sizeof(transcript_hash));
if (hash != NULL)
hash->final(hash, NULL, PTLS_HASH_FINAL_MODE_FREE);
return ret;
}
static void key_schedule_free(ptls_key_schedule_t *sched)
{
size_t i;
ptls_clear_memory(sched->secret, sizeof(sched->secret));
for (i = 0; i != sched->num_hashes; ++i) {
sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
if (sched->hashes[i].ctx_outer != NULL)
sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE);
}
free(sched);
}
static ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered, int use_outer)
{
#define FOREACH_HASH(block) \
do { \
ptls_cipher_suite_t *cs; \
if ((cs = preferred) != NULL) { \
block \
} \
if (offered != NULL) { \
size_t i, j; \
for (i = 0; (cs = offered[i]) != NULL; ++i) { \
if (preferred == NULL || cs->hash != preferred->hash) { \
for (j = 0; j != i; ++j) \
if (cs->hash == offered[j]->hash) \
break; \
if (j == i) { \
block \
} \
} \
} \
} \
} while (0)
ptls_key_schedule_t *sched;
{ /* allocate */
size_t num_hashes = 0;
FOREACH_HASH({ ++num_hashes; });
if ((sched = malloc(offsetof(ptls_key_schedule_t, hashes) + sizeof(sched->hashes[0]) * num_hashes)) == NULL)
return NULL;
*sched = (ptls_key_schedule_t){0};
}
/* setup the hash algos and contexts */
FOREACH_HASH({
sched->hashes[sched->num_hashes].algo = cs->hash;
if ((sched->hashes[sched->num_hashes].ctx = cs->hash->create()) == NULL)
goto Fail;
if (use_outer) {
if ((sched->hashes[sched->num_hashes].ctx_outer = cs->hash->create()) == NULL)
goto Fail;
} else {
sched->hashes[sched->num_hashes].ctx_outer = NULL;
}
++sched->num_hashes;
});
return sched;
Fail:
key_schedule_free(sched);
return NULL;
#undef FOREACH_HASH
}
static int key_schedule_extract(ptls_key_schedule_t *sched, ptls_iovec_t ikm)
{
int ret;
if (ikm.base == NULL)
ikm = ptls_iovec_init(zeroes_of_max_digest_size, sched->hashes[0].algo->digest_size);
if (sched->generation != 0 &&
(ret = ptls_hkdf_expand_label(sched->hashes[0].algo, sched->secret, sched->hashes[0].algo->digest_size,
ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), "derived",
ptls_iovec_init(sched->hashes[0].algo->empty_digest, sched->hashes[0].algo->digest_size),
NULL)) != 0)
return ret;
++sched->generation;
ret = ptls_hkdf_extract(sched->hashes[0].algo, sched->secret,
ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), ikm);
PTLS_DEBUGF("%s: %u, %02x%02x\n", __FUNCTION__, sched->generation, (int)sched->secret[0], (int)sched->secret[1]);
return ret;
}
static int key_schedule_select_cipher(ptls_key_schedule_t *sched, ptls_cipher_suite_t *cs, int reset)
{
size_t found_slot = SIZE_MAX, i;
int ret;
assert(sched->generation == 1);
/* find the one, while freeing others */
for (i = 0; i != sched->num_hashes; ++i) {
if (sched->hashes[i].algo == cs->hash) {
assert(found_slot == SIZE_MAX);
found_slot = i;
} else {
sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
if (sched->hashes[i].ctx_outer != NULL)
sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE);
}
}
if (found_slot != 0) {
sched->hashes[0] = sched->hashes[found_slot];
reset = 1;
}
sched->num_hashes = 1;
/* recalculate the hash if a different hash as been selected than the one we used for calculating the early secrets */
if (reset) {
--sched->generation;
memset(sched->secret, 0, sizeof(sched->secret));
if ((ret = key_schedule_extract(sched, ptls_iovec_init(NULL, 0))) != 0)
goto Exit;
}
ret = 0;
Exit:
return ret;
}
static void key_schedule_select_outer(ptls_key_schedule_t *sched)
{
/* This function is called when receiving a cleartext message (Server Hello), after the cipher-suite is determined (and hence
* the hash also), if ECH was offered */
assert(sched->generation == 1);
assert(sched->num_hashes == 1);
assert(sched->hashes[0].ctx_outer != NULL);
sched->hashes[0].ctx->final(sched->hashes[0].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
sched->hashes[0].ctx = sched->hashes[0].ctx_outer;
sched->hashes[0].ctx_outer = NULL;
}
void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen, int use_outer)
{
size_t i;
PTLS_DEBUGF("%s:%zu\n", __FUNCTION__, msglen);
for (i = 0; i != sched->num_hashes; ++i) {
ptls_hash_context_t *ctx = use_outer ? sched->hashes[i].ctx_outer : sched->hashes[i].ctx;
ctx->update(ctx, msg, msglen);
}
}
static void key_schedule_update_ch1hash_prefix(ptls_key_schedule_t *sched)
{
uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)sched->hashes[0].algo->digest_size};
ptls__key_schedule_update_hash(sched, prefix, sizeof(prefix), 0);
}
static void key_schedule_extract_ch1hash(ptls_key_schedule_t *sched, uint8_t *hash)
{
assert(sched->hashes[0].ctx_outer == NULL);
sched->hashes[0].ctx->final(sched->hashes[0].ctx, hash, PTLS_HASH_FINAL_MODE_RESET);
}
static void key_schedule_transform_post_ch1hash(ptls_key_schedule_t *sched)
{
size_t digest_size = sched->hashes[0].algo->digest_size;
ptls_hash_context_t *hashes[3] = {sched->hashes[0].ctx, sched->hashes[0].ctx_outer, NULL};
uint8_t ch1hash[PTLS_MAX_DIGEST_SIZE];
uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)digest_size};
for (size_t i = 0; hashes[i] != NULL; ++i) {
hashes[i]->final(hashes[i], ch1hash, PTLS_HASH_FINAL_MODE_RESET);
hashes[i]->update(hashes[i], prefix, sizeof(prefix));
hashes[i]->update(hashes[i], ch1hash, digest_size);
}
ptls_clear_memory(ch1hash, sizeof(ch1hash));
}
static int derive_secret_with_hash(ptls_key_schedule_t *sched, void *secret, const char *label, const uint8_t *hash)
{
int ret = ptls_hkdf_expand_label(sched->hashes[0].algo, secret, sched->hashes[0].algo->digest_size,
ptls_iovec_init(sched->secret, sched->hashes[0].algo->digest_size), label,
ptls_iovec_init(hash, sched->hashes[0].algo->digest_size), NULL);
PTLS_DEBUGF("%s: (label=%s, hash=%02x%02x) => %02x%02x\n", __FUNCTION__, label, hash[0], hash[1], ((uint8_t *)secret)[0],
((uint8_t *)secret)[1]);
return ret;
}
static int derive_secret(ptls_key_schedule_t *sched, void *secret, const char *label)
{
uint8_t hash_value[PTLS_MAX_DIGEST_SIZE];
sched->hashes[0].ctx->final(sched->hashes[0].ctx, hash_value, PTLS_HASH_FINAL_MODE_SNAPSHOT);
int ret = derive_secret_with_hash(sched, secret, label, hash_value);
ptls_clear_memory(hash_value, sizeof(hash_value));
return ret;
}
static int derive_secret_with_empty_digest(ptls_key_schedule_t *sched, void *secret, const char *label)
{
return derive_secret_with_hash(sched, secret, label, sched->hashes[0].algo->empty_digest);
}
static int derive_exporter_secret(ptls_t *tls, int is_early)
{
int ret;
if (!tls->ctx->use_exporter)
return 0;
uint8_t **slot = is_early ? &tls->exporter_master_secret.early : &tls->exporter_master_secret.one_rtt;
assert(*slot == NULL);
if ((*slot = malloc(tls->key_schedule->hashes[0].algo->digest_size)) == NULL)
return PTLS_ERROR_NO_MEMORY;
if ((ret = derive_secret(tls->key_schedule, *slot, is_early ? "e exp master" : "exp master")) != 0)
return ret;
log_secret(tls, is_early ? "EARLY_EXPORTER_SECRET" : "EXPORTER_SECRET",
ptls_iovec_init(*slot, tls->key_schedule->hashes[0].algo->digest_size));
return 0;
}
static void free_exporter_master_secret(ptls_t *tls, int is_early)
{
uint8_t *slot = is_early ? tls->exporter_master_secret.early : tls->exporter_master_secret.one_rtt;
if (slot == NULL)
return;
assert(tls->key_schedule != NULL);
ptls_clear_memory(slot, tls->key_schedule->hashes[0].algo->digest_size);
free(slot);
}
static int derive_resumption_secret(ptls_key_schedule_t *sched, uint8_t *secret, ptls_iovec_t nonce)
{
int ret;
if ((ret = derive_secret(sched, secret, "res master")) != 0)
goto Exit;
if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, secret, sched->hashes[0].algo->digest_size,
ptls_iovec_init(secret, sched->hashes[0].algo->digest_size), "resumption", nonce, NULL)) != 0)
goto Exit;
Exit:
if (ret != 0)
ptls_clear_memory(secret, sched->hashes[0].algo->digest_size);
return ret;
}
static int decode_new_session_ticket(ptls_t *tls, uint32_t *lifetime, uint32_t *age_add, ptls_iovec_t *nonce, ptls_iovec_t *ticket,
uint32_t *max_early_data_size, const uint8_t *src, const uint8_t *const end)
{
uint16_t exttype;
int ret;
if ((ret = ptls_decode32(lifetime, &src, end)) != 0)
goto Exit;
if ((ret = ptls_decode32(age_add, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 1, {
*nonce = ptls_iovec_init(src, end - src);
src = end;
});
ptls_decode_open_block(src, end, 2, {
if (src == end) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
*ticket = ptls_iovec_init(src, end - src);
src = end;
});
*max_early_data_size = 0;
decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, &exttype, {
if (tls->ctx->on_extension != NULL &&
(ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, exttype,
ptls_iovec_init(src, end - src)) != 0))
goto Exit;
switch (exttype) {
case PTLS_EXTENSION_TYPE_EARLY_DATA:
if ((ret = ptls_decode32(max_early_data_size, &src, end)) != 0)
goto Exit;
break;
default:
src = end;
break;
}
});
ret = 0;
Exit:
return ret;
}
static int decode_stored_session_ticket(ptls_t *tls, ptls_key_exchange_algorithm_t **key_share, ptls_cipher_suite_t **cs,
ptls_iovec_t *secret, uint32_t *obfuscated_ticket_age, ptls_iovec_t *ticket,
uint32_t *max_early_data_size, const uint8_t *src, const uint8_t *const end)
{
uint16_t kxid, csid;
uint32_t lifetime, age_add;
uint64_t obtained_at, now;
ptls_iovec_t nonce;
int ret;
/* decode */
if ((ret = ptls_decode64(&obtained_at, &src, end)) != 0)
goto Exit;
if ((ret = ptls_decode16(&kxid, &src, end)) != 0)
goto Exit;
if ((ret = ptls_decode16(&csid, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 3, {
if ((ret = decode_new_session_ticket(tls, &lifetime, &age_add, &nonce, ticket, max_early_data_size, src, end)) != 0)
goto Exit;
src = end;
});
ptls_decode_block(src, end, 2, {
*secret = ptls_iovec_init(src, end - src);
src = end;
});
{ /* determine the key-exchange */
ptls_key_exchange_algorithm_t **cand;
for (cand = tls->ctx->key_exchanges; *cand != NULL; ++cand)
if ((*cand)->id == kxid)
break;
if (*cand == NULL) {
ret = PTLS_ERROR_LIBRARY;
goto Exit;
}
*key_share = *cand;
}
{ /* determine the cipher-suite */
ptls_cipher_suite_t **cand;
for (cand = tls->ctx->cipher_suites; *cand != NULL; ++cand)
if ((*cand)->id == csid)
break;
if (*cand == NULL) {
ret = PTLS_ERROR_LIBRARY;
goto Exit;
}
*cs = *cand;
}
/* calculate obfuscated_ticket_age */
now = tls->ctx->get_time->cb(tls->ctx->get_time);
if (!(obtained_at <= now && now - obtained_at < 7 * 86400 * 1000)) {
ret = PTLS_ERROR_LIBRARY;
goto Exit;
}
*obfuscated_ticket_age = (uint32_t)(now - obtained_at) + age_add;
ret = 0;
Exit:
return ret;
}
static int get_traffic_key(ptls_hash_algorithm_t *algo, void *key, size_t key_size, int is_iv, const void *secret,
ptls_iovec_t hash_value, const char *label_prefix)
{
return ptls_hkdf_expand_label(algo, key, key_size, ptls_iovec_init(secret, algo->digest_size), is_iv ? "iv" : "key", hash_value,
label_prefix);
}
static int get_traffic_keys(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, void *key, void *iv, const void *secret,
ptls_iovec_t hash_value, const char *label_prefix)
{
int ret;
if ((ret = get_traffic_key(hash, key, aead->key_size, 0, secret, hash_value, label_prefix)) != 0 ||
(ret = get_traffic_key(hash, iv, aead->iv_size, 1, secret, hash_value, label_prefix)) != 0) {
ptls_clear_memory(key, aead->key_size);
ptls_clear_memory(iv, aead->iv_size);
}
return ret;
}
static int setup_traffic_protection(ptls_t *tls, int is_enc, const char *secret_label, size_t epoch, int skip_notify)
{
static const char *log_labels[2][4] = {
{NULL, "CLIENT_EARLY_TRAFFIC_SECRET", "CLIENT_HANDSHAKE_TRAFFIC_SECRET", "CLIENT_TRAFFIC_SECRET_0"},
{NULL, NULL, "SERVER_HANDSHAKE_TRAFFIC_SECRET", "SERVER_TRAFFIC_SECRET_0"}};
struct st_ptls_traffic_protection_t *ctx = is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec;
if (secret_label != NULL) {
int ret;
if ((ret = derive_secret(tls->key_schedule, ctx->secret, secret_label)) != 0)
return ret;
}
ctx->epoch = epoch;
log_secret(tls, log_labels[ptls_is_server(tls) == is_enc][epoch],
ptls_iovec_init(ctx->secret, tls->key_schedule->hashes[0].algo->digest_size));
/* special path for applications having their own record layer */
if (tls->ctx->update_traffic_key != NULL) {
if (skip_notify)
return 0;
return tls->ctx->update_traffic_key->cb(tls->ctx->update_traffic_key, tls, is_enc, epoch, ctx->secret);
}
if (ctx->aead != NULL)
ptls_aead_free(ctx->aead);
if ((ctx->aead = ptls_aead_new(tls->cipher_suite->aead, tls->cipher_suite->hash, is_enc, ctx->secret,
tls->ctx->hkdf_label_prefix__obsolete)) == NULL)
return PTLS_ERROR_NO_MEMORY; /* TODO obtain error from ptls_aead_new */
ctx->seq = 0;
PTLS_DEBUGF("[%s] %02x%02x,%02x%02x\n", log_labels[ptls_is_server(tls)][epoch], (unsigned)ctx->secret[0],
(unsigned)ctx->secret[1], (unsigned)ctx->aead->static_iv[0], (unsigned)ctx->aead->static_iv[1]);
return 0;
}
static int commission_handshake_secret(ptls_t *tls)
{
int is_enc = !ptls_is_server(tls);
assert(tls->pending_handshake_secret != NULL);
memcpy((is_enc ? &tls->traffic_protection.enc : &tls->traffic_protection.dec)->secret, tls->pending_handshake_secret,
PTLS_MAX_DIGEST_SIZE);
ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE);
free(tls->pending_handshake_secret);
tls->pending_handshake_secret = NULL;
return setup_traffic_protection(tls, is_enc, NULL, 2, 1);
}
static void log_client_random(ptls_t *tls)
{
char buf[sizeof(tls->client_random) * 2 + 1];
PTLS_PROBE(CLIENT_RANDOM, tls, ptls_hexdump(buf, tls->client_random, sizeof(tls->client_random)));
PTLS_LOG_CONN(client_random, tls, { PTLS_LOG_ELEMENT_HEXDUMP(bytes, tls->client_random, sizeof(tls->client_random)); });
}
#define SESSION_IDENTIFIER_MAGIC "ptls0001" /* the number should be changed upon incompatible format change */
#define SESSION_IDENTIFIER_MAGIC_SIZE (sizeof(SESSION_IDENTIFIER_MAGIC) - 1)
static int encode_session_identifier(ptls_context_t *ctx, ptls_buffer_t *buf, uint32_t ticket_age_add, ptls_iovec_t ticket_nonce,
ptls_key_schedule_t *sched, const char *server_name, uint16_t key_exchange_id, uint16_t csid,
const char *negotiated_protocol)
{
int ret = 0;
ptls_buffer_push_block(buf, 2, {
/* format id */
ptls_buffer_pushv(buf, SESSION_IDENTIFIER_MAGIC, SESSION_IDENTIFIER_MAGIC_SIZE);
/* date */
ptls_buffer_push64(buf, ctx->get_time->cb(ctx->get_time));
/* resumption master secret */
ptls_buffer_push_block(buf, 2, {
if ((ret = ptls_buffer_reserve(buf, sched->hashes[0].algo->digest_size)) != 0)
goto Exit;
if ((ret = derive_resumption_secret(sched, buf->base + buf->off, ticket_nonce)) != 0)
goto Exit;
buf->off += sched->hashes[0].algo->digest_size;
});
/* key-exchange */
ptls_buffer_push16(buf, key_exchange_id);
/* cipher-suite */
ptls_buffer_push16(buf, csid);
/* ticket_age_add */
ptls_buffer_push32(buf, ticket_age_add);
/* server-name */
ptls_buffer_push_block(buf, 2, {
if (server_name != NULL)
ptls_buffer_pushv(buf, server_name, strlen(server_name));
});
/* alpn */
ptls_buffer_push_block(buf, 1, {
if (negotiated_protocol != NULL)
ptls_buffer_pushv(buf, negotiated_protocol, strlen(negotiated_protocol));
});
});
Exit:
return ret;
}
int decode_session_identifier(uint64_t *issued_at, ptls_iovec_t *psk, uint32_t *ticket_age_add, ptls_iovec_t *server_name,
uint16_t *key_exchange_id, uint16_t *csid, ptls_iovec_t *negotiated_protocol, const uint8_t *src,
const uint8_t *const end)
{
int ret = 0;
ptls_decode_block(src, end, 2, {
if (end - src < SESSION_IDENTIFIER_MAGIC_SIZE ||
memcmp(src, SESSION_IDENTIFIER_MAGIC, SESSION_IDENTIFIER_MAGIC_SIZE) != 0) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
src += SESSION_IDENTIFIER_MAGIC_SIZE;
if ((ret = ptls_decode64(issued_at, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 2, {
*psk = ptls_iovec_init(src, end - src);
src = end;
});
if ((ret = ptls_decode16(key_exchange_id, &src, end)) != 0)
goto Exit;
if ((ret = ptls_decode16(csid, &src, end)) != 0)
goto Exit;
if ((ret = ptls_decode32(ticket_age_add, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 2, {
*server_name = ptls_iovec_init(src, end - src);
src = end;
});
ptls_decode_open_block(src, end, 1, {
*negotiated_protocol = ptls_iovec_init(src, end - src);
src = end;
});
});
Exit:
return ret;
}
static size_t build_certificate_verify_signdata(uint8_t *data, ptls_key_schedule_t *sched, const char *context_string)
{
size_t datalen = 0;
memset(data + datalen, 32, 64);
datalen += 64;
memcpy(data + datalen, context_string, strlen(context_string) + 1);
datalen += strlen(context_string) + 1;
sched->hashes[0].ctx->final(sched->hashes[0].ctx, data + datalen, PTLS_HASH_FINAL_MODE_SNAPSHOT);
datalen += sched->hashes[0].algo->digest_size;
assert(datalen <= PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE);
return datalen;
}
static int calc_verify_data(void *output, ptls_key_schedule_t *sched, const void *secret)
{
ptls_hash_context_t *hmac;
uint8_t digest[PTLS_MAX_DIGEST_SIZE];
int ret;
if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, digest, sched->hashes[0].algo->digest_size,
ptls_iovec_init(secret, sched->hashes[0].algo->digest_size), "finished",
ptls_iovec_init(NULL, 0), NULL)) != 0)
return ret;
if ((hmac = ptls_hmac_create(sched->hashes[0].algo, digest, sched->hashes[0].algo->digest_size)) == NULL) {
ptls_clear_memory(digest, sizeof(digest));
return PTLS_ERROR_NO_MEMORY;
}
sched->hashes[0].ctx->final(sched->hashes[0].ctx, digest, PTLS_HASH_FINAL_MODE_SNAPSHOT);
PTLS_DEBUGF("%s: %02x%02x,%02x%02x\n", __FUNCTION__, ((uint8_t *)secret)[0], ((uint8_t *)secret)[1], digest[0], digest[1]);
hmac->update(hmac, digest, sched->hashes[0].algo->digest_size);
ptls_clear_memory(digest, sizeof(digest));
hmac->final(hmac, output, PTLS_HASH_FINAL_MODE_FREE);
return 0;
}
static int verify_finished(ptls_t *tls, ptls_iovec_t message)
{
uint8_t verify_data[PTLS_MAX_DIGEST_SIZE];
int ret;
if (PTLS_HANDSHAKE_HEADER_SIZE + tls->key_schedule->hashes[0].algo->digest_size != message.len) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
if ((ret = calc_verify_data(verify_data, tls->key_schedule, tls->traffic_protection.dec.secret)) != 0)
goto Exit;
if (!ptls_mem_equal(message.base + PTLS_HANDSHAKE_HEADER_SIZE, verify_data, tls->key_schedule->hashes[0].algo->digest_size)) {
ret = PTLS_ALERT_HANDSHAKE_FAILURE;
goto Exit;
}
Exit:
ptls_clear_memory(verify_data, sizeof(verify_data));
return ret;
}
static int send_finished(ptls_t *tls, ptls_message_emitter_t *emitter)
{
int ret;
ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, {
if ((ret = ptls_buffer_reserve(emitter->buf, tls->key_schedule->hashes[0].algo->digest_size)) != 0)
goto Exit;
if ((ret = calc_verify_data(emitter->buf->base + emitter->buf->off, tls->key_schedule,
tls->traffic_protection.enc.secret)) != 0)
goto Exit;
emitter->buf->off += tls->key_schedule->hashes[0].algo->digest_size;
});
Exit:
return ret;
}
static int send_session_ticket(ptls_t *tls, ptls_message_emitter_t *emitter)
{
ptls_hash_context_t *msghash_backup = tls->key_schedule->hashes[0].ctx->clone_(tls->key_schedule->hashes[0].ctx);
ptls_buffer_t session_id;
char session_id_smallbuf[128];
uint32_t ticket_age_add;
int ret = 0;
assert(tls->ctx->ticket_lifetime != 0);
assert(tls->ctx->encrypt_ticket != NULL);
ptls_buffer_init(&session_id, session_id_smallbuf, sizeof(session_id_smallbuf));
{ /* calculate verify-data that will be sent by the client */
size_t orig_off = emitter->buf->off;
if (tls->pending_handshake_secret != NULL && !tls->ctx->omit_end_of_early_data) {
assert(tls->state == PTLS_STATE_SERVER_EXPECT_END_OF_EARLY_DATA);
ptls_buffer_push_message_body(emitter->buf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA, {});
emitter->buf->off = orig_off;
}
ptls_buffer_push_message_body(emitter->buf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, {
if ((ret = ptls_buffer_reserve(emitter->buf, tls->key_schedule->hashes[0].algo->digest_size)) != 0)
goto Exit;
if ((ret = calc_verify_data(emitter->buf->base + emitter->buf->off, tls->key_schedule,
tls->pending_handshake_secret != NULL ? tls->pending_handshake_secret
: tls->traffic_protection.dec.secret)) != 0)
goto Exit;
emitter->buf->off += tls->key_schedule->hashes[0].algo->digest_size;
});
emitter->buf->off = orig_off;
}
tls->ctx->random_bytes(&ticket_age_add, sizeof(ticket_age_add));
/* build the raw nsk */
ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule,
tls->server_name, tls->key_share->id, tls->cipher_suite->id, tls->negotiated_protocol);
if (ret != 0)
goto Exit;
/* encrypt and send */
ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, {
ptls_buffer_push32(emitter->buf, tls->ctx->ticket_lifetime);
ptls_buffer_push32(emitter->buf, ticket_age_add);
ptls_buffer_push_block(emitter->buf, 1, {});
ptls_buffer_push_block(emitter->buf, 2, {
if ((ret = tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 1, emitter->buf,
ptls_iovec_init(session_id.base, session_id.off))) != 0)
goto Exit;
});
ptls_buffer_push_block(emitter->buf, 2, {
if (tls->ctx->max_early_data_size != 0)
buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_EARLY_DATA,
{ ptls_buffer_push32(emitter->buf, tls->ctx->max_early_data_size); });
});
});
Exit:
ptls_buffer_dispose(&session_id);
/* restore handshake state */
tls->key_schedule->hashes[0].ctx->final(tls->key_schedule->hashes[0].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
tls->key_schedule->hashes[0].ctx = msghash_backup;
return ret;
}
static int push_change_cipher_spec(ptls_t *tls, ptls_message_emitter_t *emitter)
{
int ret;
/* check if we are requested to (or still need to) */
if (!tls->send_change_cipher_spec) {
ret = 0;
goto Exit;
}
/* CCS is a record, can only be sent when using a record-based protocol. */
if (emitter->begin_message != begin_record_message) {
ret = PTLS_ALERT_UNEXPECTED_MESSAGE;
goto Exit;
}
/* emit CCS */
buffer_push_record(emitter->buf, PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC, { ptls_buffer_push(emitter->buf, 1); });
tls->send_change_cipher_spec = 0;
ret = 0;
Exit:
return ret;
}
static int push_additional_extensions(ptls_handshake_properties_t *properties, ptls_buffer_t *sendbuf)
{
int ret;
if (properties != NULL && properties->additional_extensions != NULL) {
ptls_raw_extension_t *ext;
for (ext = properties->additional_extensions; ext->type != UINT16_MAX; ++ext) {
buffer_push_extension(sendbuf, ext->type, { ptls_buffer_pushv(sendbuf, ext->data.base, ext->data.len); });
}
}
ret = 0;
Exit:
return ret;
}
static int push_signature_algorithms(ptls_verify_certificate_t *vc, ptls_buffer_t *sendbuf)
{
/* The list sent when verify callback is not registered */
static const uint16_t default_algos[] = {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256,
PTLS_SIGNATURE_RSA_PKCS1_SHA256, PTLS_SIGNATURE_RSA_PKCS1_SHA1, UINT16_MAX};
int ret;
ptls_buffer_push_block(sendbuf, 2, {
for (const uint16_t *p = vc != NULL ? vc->algos : default_algos; *p != UINT16_MAX; ++p)
ptls_buffer_push16(sendbuf, *p);
});
ret = 0;
Exit:
return ret;
}
static int decode_signature_algorithms(struct st_ptls_signature_algorithms_t *sa, const uint8_t **src, const uint8_t *end)
{
int ret;
ptls_decode_block(*src, end, 2, {
do {
uint16_t id;
if ((ret = ptls_decode16(&id, src, end)) != 0)
goto Exit;
if (sa->count < PTLS_ELEMENTSOF(sa->list))
sa->list[sa->count++] = id;
} while (*src != end);
});
ret = 0;
Exit:
return ret;
}
static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **candidates, const uint8_t *src,
const uint8_t *const end, int server_preference, int server_chacha_priority)
{
size_t found_index = SIZE_MAX;
int ret;
while (src != end) {
uint16_t id;
if ((ret = ptls_decode16(&id, &src, end)) != 0)
goto Exit;
for (size_t i = 0; candidates[i] != NULL; ++i) {
if (candidates[i]->id == id) {
if (server_preference && !(server_chacha_priority && id == PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)) {
/* preserve smallest matching index, and proceed to the next input */
if (i < found_index) {
found_index = i;
break;
}
} else {
/* return the pointer matching to the first input that can be used */
*selected = candidates[i];
goto Exit;
}
}
}
/* first position of the server list matched (server_preference) */
if (found_index == 0)
break;
/* server preference is overridden only if the first entry of client-provided list is chachapoly */
server_chacha_priority = 0;
}
if (found_index != SIZE_MAX) {
*selected = candidates[found_index];
ret = 0;
} else {
ret = PTLS_ALERT_HANDSHAKE_FAILURE;
}
Exit:
return ret;
}
static int push_key_share_entry(ptls_buffer_t *buf, uint16_t group, ptls_iovec_t pubkey)
{
int ret;
ptls_buffer_push16(buf, group);
ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, pubkey.base, pubkey.len); });
ret = 0;
Exit:
return ret;
}
static int decode_key_share_entry(uint16_t *group, ptls_iovec_t *key_exchange, const uint8_t **src, const uint8_t *const end)
{
int ret;
if ((ret = ptls_decode16(group, src, end)) != 0)
goto Exit;
ptls_decode_open_block(*src, end, 2, {
*key_exchange = ptls_iovec_init(*src, end - *src);
*src = end;
});
Exit:
return ret;
}
static int select_key_share(ptls_key_exchange_algorithm_t **selected, ptls_iovec_t *peer_key,
ptls_key_exchange_algorithm_t **candidates, const uint8_t **src, const uint8_t *const end,
int expect_one)
{
int ret;
*selected = NULL;
if (expect_one && *src == end) {
ret = PTLS_ALERT_ILLEGAL_PARAMETER;
goto Exit;
}
while (*src != end) {
uint16_t group;
ptls_iovec_t key;
if ((ret = decode_key_share_entry(&group, &key, src, end)) != 0)
goto Exit;
ptls_key_exchange_algorithm_t **c = candidates;
for (; *c != NULL; ++c) {
if (*selected == NULL && (*c)->id == group) {
*selected = *c;
*peer_key = key;
}
}
if (expect_one) {
ret = *selected != NULL ? 0 : PTLS_ALERT_ILLEGAL_PARAMETER;
goto Exit;
}
}
ret = 0;
Exit:
return ret;
}
static int emit_server_name_extension(ptls_buffer_t *buf, const char *server_name)
{
int ret;
ptls_buffer_push_block(buf, 2, {
ptls_buffer_push(buf, PTLS_SERVER_NAME_TYPE_HOSTNAME);
ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, server_name, strlen(server_name)); });
});
ret = 0;
Exit:
return ret;
}
/**
* Within the outer ECH extension, returns the number of bytes that preceeds the AEAD-encrypted payload.
*/
static inline size_t outer_ech_header_size(size_t enc_size)
{
return 10 + enc_size;
}
/**
* Flag to indicate which of ClientHelloInner, EncodedClientHelloInner, ClientHelloOuter is to be generated. When ECH is inactive,
* only ClientHelloInner is used.
*/
enum encode_ch_mode { ENCODE_CH_MODE_INNER, ENCODE_CH_MODE_ENCODED_INNER, ENCODE_CH_MODE_OUTER };
static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum encode_ch_mode mode, int is_second_flight,
ptls_handshake_properties_t *properties, const void *client_random,
ptls_key_exchange_context_t *key_share_ctx, const char *sni_name, ptls_iovec_t legacy_session_id,
struct st_ptls_ech_t *ech, size_t *ech_size_offset, ptls_iovec_t ech_replay,
ptls_iovec_t resumption_secret, ptls_iovec_t resumption_ticket, uint32_t obfuscated_ticket_age,
size_t psk_binder_size, ptls_iovec_t *cookie, int using_early_data)
{
int ret;
assert(mode == ENCODE_CH_MODE_INNER || ech != NULL);
ptls_buffer_push_message_body(sendbuf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
/* legacy_version */
ptls_buffer_push16(sendbuf, 0x0303);
/* random_bytes */
ptls_buffer_pushv(sendbuf, client_random, PTLS_HELLO_RANDOM_SIZE);
/* lecagy_session_id */
ptls_buffer_push_block(sendbuf, 1, {
if (mode != ENCODE_CH_MODE_ENCODED_INNER)
ptls_buffer_pushv(sendbuf, legacy_session_id.base, legacy_session_id.len);
});
/* cipher_suites */
ptls_buffer_push_block(sendbuf, 2, {
ptls_cipher_suite_t **cs = ctx->cipher_suites;
for (; *cs != NULL; ++cs)
ptls_buffer_push16(sendbuf, (*cs)->id);
});
/* legacy_compression_methods */
ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, 0); });
/* extensions */
ptls_buffer_push_block(sendbuf, 2, {
if (mode == ENCODE_CH_MODE_OUTER) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
size_t ext_payload_from = sendbuf->off;
ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_OUTER);
ptls_buffer_push16(sendbuf, ech->cipher->id.kdf);
ptls_buffer_push16(sendbuf, ech->cipher->id.aead);
ptls_buffer_push(sendbuf, ech->config_id);
ptls_buffer_push_block(sendbuf, 2, {
if (!is_second_flight)
ptls_buffer_pushv(sendbuf, ech->client.enc.base, ech->client.enc.len);
});
ptls_buffer_push_block(sendbuf, 2, {
assert(sendbuf->off - ext_payload_from ==
outer_ech_header_size(is_second_flight ? 0 : ech->client.enc.len));
if ((ret = ptls_buffer_reserve(sendbuf, *ech_size_offset)) != 0)
goto Exit;
memset(sendbuf->base + sendbuf->off, 0, *ech_size_offset);
sendbuf->off += *ech_size_offset;
*ech_size_offset = sendbuf->off - *ech_size_offset;
});
});
} else if (ech->aead != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO,
{ ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_INNER); });
} else if (ech_replay.base != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO,
{ ptls_buffer_pushv(sendbuf, ech_replay.base, ech_replay.len); });
}
if (mode == ENCODE_CH_MODE_ENCODED_INNER) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS, {
ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push16(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE); });
});
} else {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
ptls_buffer_push_block(sendbuf, 2, {
if (key_share_ctx != NULL &&
(ret = push_key_share_entry(sendbuf, key_share_ctx->algo->id, key_share_ctx->pubkey)) != 0)
goto Exit;
});
});
}
if (sni_name != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
if ((ret = emit_server_name_extension(sendbuf, sni_name)) != 0)
goto Exit;
});
}
if (properties != NULL && properties->client.negotiated_protocols.count != 0) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
ptls_buffer_push_block(sendbuf, 2, {
size_t i;
for (i = 0; i != properties->client.negotiated_protocols.count; ++i) {
ptls_buffer_push_block(sendbuf, 1, {
ptls_iovec_t p = properties->client.negotiated_protocols.list[i];
ptls_buffer_pushv(sendbuf, p.base, p.len);
});
}
});
});
}
if (ctx->decompress_certificate != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE, {
ptls_buffer_push_block(sendbuf, 1, {
const uint16_t *algo = ctx->decompress_certificate->supported_algorithms;
assert(*algo != UINT16_MAX);
for (; *algo != UINT16_MAX; ++algo)
ptls_buffer_push16(sendbuf, *algo);
});
});
}
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, {
ptls_buffer_push_block(sendbuf, 1, {
size_t i;
for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i)
ptls_buffer_push16(sendbuf, supported_versions[i]);
});
});
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
if ((ret = push_signature_algorithms(ctx->verify_certificate, sendbuf)) != 0)
goto Exit;
});
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, {
ptls_key_exchange_algorithm_t **algo = ctx->key_exchanges;
ptls_buffer_push_block(sendbuf, 2, {
for (; *algo != NULL; ++algo)
ptls_buffer_push16(sendbuf, (*algo)->id);
});
});
if (cookie != NULL && cookie->base != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); });
});
}
if (ctx->use_raw_public_keys) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, {
ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
});
}
if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
if (ctx->save_ticket != NULL || resumption_secret.base != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES, {
ptls_buffer_push_block(sendbuf, 1, {
if (!ctx->require_dhe_on_psk)
ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK);
ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK_DHE);
});
});
}
if (resumption_secret.base != NULL) {
if (using_early_data && !is_second_flight)
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
/* pre-shared key "MUST be the last extension in the ClientHello" (draft-17 section 4.2.6) */
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, {
ptls_buffer_push_block(sendbuf, 2, {
ptls_buffer_push_block(sendbuf, 2, {
if (mode == ENCODE_CH_MODE_OUTER) {
if ((ret = ptls_buffer_reserve(sendbuf, resumption_ticket.len)) != 0)
goto Exit;
ctx->random_bytes(sendbuf->base + sendbuf->off, resumption_ticket.len);
sendbuf->off += resumption_ticket.len;
} else {
ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len);
}
});
uint32_t age;
if (mode == ENCODE_CH_MODE_OUTER) {
ctx->random_bytes(&age, sizeof(age));
} else {
age = obfuscated_ticket_age;
}
ptls_buffer_push32(sendbuf, age);
});
/* allocate space for PSK binder. The space is filled initially filled by a random value (meeting the
* requirement of ClientHelloOuter), and later gets filled with the correct binder value if necessary. */
ptls_buffer_push_block(sendbuf, 2, {
ptls_buffer_push_block(sendbuf, 1, {
if ((ret = ptls_buffer_reserve(sendbuf, psk_binder_size)) != 0)
goto Exit;
ctx->random_bytes(sendbuf->base + sendbuf->off, psk_binder_size);
sendbuf->off += psk_binder_size;
});
});
});
}
});
});
Exit:
return ret;
}
static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_handshake_properties_t *properties,
ptls_iovec_t *cookie)
{
ptls_iovec_t resumption_secret = {NULL}, resumption_ticket = {NULL};
uint32_t obfuscated_ticket_age = 0;
const char *sni_name = NULL;
size_t mess_start, msghash_off;
uint8_t binder_key[PTLS_MAX_DIGEST_SIZE];
ptls_buffer_t encoded_ch_inner;
int ret, is_second_flight = tls->key_schedule != NULL;
ptls_buffer_init(&encoded_ch_inner, "", 0);
if (tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name))
sni_name = tls->server_name;
if (properties != NULL) {
/* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */
if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL) {
if (properties->client.ech.configs.len != 0) {
struct st_decoded_ech_config_t decoded;
client_decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs);
if (decoded.kem != NULL && decoded.cipher != NULL) {
if ((ret = client_setup_ech(&tls->ech, &decoded, tls->ctx->random_bytes)) != 0)
goto Exit;
}
} else {
/* zero-length config indicates ECH greasing */
client_setup_ech_grease(&tls->ech, tls->ctx->random_bytes, tls->ctx->ech.client.kems, tls->ctx->ech.client.ciphers,
sni_name);
}
}
/* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */
if (properties->client.session_ticket.base != NULL) {
ptls_key_exchange_algorithm_t *key_share = NULL;
ptls_cipher_suite_t *cipher_suite = NULL;
uint32_t max_early_data_size;
if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &resumption_secret, &obfuscated_ticket_age,
&resumption_ticket, &max_early_data_size, properties->client.session_ticket.base,
properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) {
tls->client.offered_psk = 1;
/* key-share selected by HRR should not be overridden */
if (tls->key_share == NULL)
tls->key_share = key_share;
tls->cipher_suite = cipher_suite;
if (!is_second_flight && max_early_data_size != 0 && properties->client.max_early_data_size != NULL) {
tls->client.using_early_data = 1;
*properties->client.max_early_data_size = max_early_data_size;
}
} else {
resumption_secret = ptls_iovec_init(NULL, 0);
}
}
if (tls->client.using_early_data) {
properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN;
} else {
if (properties->client.max_early_data_size != NULL)
*properties->client.max_early_data_size = 0;
properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED;
}
}
/* use the default key share if still not undetermined */
if (tls->key_share == NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange))
tls->key_share = tls->ctx->key_exchanges[0];
/* instantiate key share context */
assert(tls->client.key_share_ctx == NULL);
if (tls->key_share != NULL) {
if ((ret = tls->key_share->create(tls->key_share, &tls->client.key_share_ctx)) != 0)
goto Exit;
}
/* initialize key schedule */
if (!is_second_flight) {
tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites, tls->ech.aead != NULL);
if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0)
goto Exit;
}
/* start generating CH */
if ((ret = emitter->begin_message(emitter)) != 0)
goto Exit;
mess_start = msghash_off = emitter->buf->off;
/* generate true (inner) CH */
if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_INNER, is_second_flight, properties,
tls->ech.aead != NULL ? tls->ech.inner_client_random : tls->client_random,
tls->client.key_share_ctx, sni_name, tls->client.legacy_session_id, &tls->ech, NULL,
tls->ech.client.first_ech, resumption_secret, resumption_ticket, obfuscated_ticket_age,
tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0)
goto Exit;
/* update the message hash, filling in the PSK binder HMAC if necessary */
if (resumption_secret.base != NULL) {
size_t psk_binder_off = emitter->buf->off - (3 + tls->key_schedule->hashes[0].algo->digest_size);
if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, "res binder")) != 0)
goto Exit;
ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off, 0);
msghash_off = psk_binder_off;
if ((ret = calc_verify_data(emitter->buf->base + psk_binder_off + 3, tls->key_schedule, binder_key)) != 0)
goto Exit;
}
ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, emitter->buf->off - msghash_off, 0);
/* ECH */
if (tls->ech.aead != NULL) {
/* build EncodedCHInner */
if ((ret = encode_client_hello(tls->ctx, &encoded_ch_inner, ENCODE_CH_MODE_ENCODED_INNER, is_second_flight, properties,
tls->ech.inner_client_random, tls->client.key_share_ctx, sni_name,
tls->client.legacy_session_id, &tls->ech, NULL, ptls_iovec_init(NULL, 0), resumption_secret,
resumption_ticket, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size,
cookie, tls->client.using_early_data)) != 0)
goto Exit;
if (resumption_secret.base != NULL)
memcpy(encoded_ch_inner.base + encoded_ch_inner.off - tls->key_schedule->hashes[0].algo->digest_size,
emitter->buf->base + emitter->buf->off - tls->key_schedule->hashes[0].algo->digest_size,
tls->key_schedule->hashes[0].algo->digest_size);
{ /* pad EncodedCHInner (following draft-ietf-tls-esni-15 6.1.3) */
size_t padding_len;
if (sni_name != NULL) {
padding_len = strlen(sni_name);
if (padding_len < tls->ech.client.max_name_length)
padding_len = tls->ech.client.max_name_length;
} else {
padding_len = tls->ech.client.max_name_length + 9;
}
size_t final_len = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + padding_len;
final_len = (final_len + 31) / 32 * 32;
padding_len = final_len - (encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE);
if (padding_len != 0) {
if ((ret = ptls_buffer_reserve(&encoded_ch_inner, padding_len)) != 0)
goto Exit;
memset(encoded_ch_inner.base + encoded_ch_inner.off, 0, padding_len);
encoded_ch_inner.off += padding_len;
}
}
/* flush CHInner, build CHOuterAAD */
emitter->buf->off = mess_start;
size_t ech_payload_size = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + tls->ech.aead->algo->tag_size,
ech_size_offset = ech_payload_size;
if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_OUTER, is_second_flight, properties,
tls->client_random, tls->client.key_share_ctx, tls->ech.client.public_name,
tls->client.legacy_session_id, &tls->ech, &ech_size_offset, ptls_iovec_init(NULL, 0),
resumption_secret, resumption_ticket, obfuscated_ticket_age,
tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0)
goto Exit;
/* overwrite ECH payload */
ptls_aead_encrypt(tls->ech.aead, emitter->buf->base + ech_size_offset, encoded_ch_inner.base + PTLS_HANDSHAKE_HEADER_SIZE,
encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE, is_second_flight,
emitter->buf->base + mess_start + PTLS_HANDSHAKE_HEADER_SIZE,
emitter->buf->off - (mess_start + PTLS_HANDSHAKE_HEADER_SIZE));
/* keep the copy of the 1st ECH extension so that we can send it again in 2nd CH in response to rejection with HRR */
if (!is_second_flight) {
size_t len = outer_ech_header_size(tls->ech.client.enc.len) + ech_payload_size;
if ((tls->ech.client.first_ech.base = malloc(len)) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
memcpy(tls->ech.client.first_ech.base,
emitter->buf->base + ech_size_offset - outer_ech_header_size(tls->ech.client.enc.len), len);
tls->ech.client.first_ech.len = len;
if (properties->client.ech.configs.len != 0) {
tls->ech.offered = 1;
} else {
tls->ech.offered_grease = 1;
}
}
/* update hash */
ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + mess_start, emitter->buf->off - mess_start, 1);
}
/* commit CH to the record layer */
if ((ret = emitter->commit_message(emitter)) != 0)
goto Exit;
if (tls->client.using_early_data) {
assert(!is_second_flight);
if ((ret = setup_traffic_protection(tls, 1, "c e traffic", 1, 0)) != 0)
goto Exit;
if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
goto Exit;
}
if (resumption_secret.base != NULL && !is_second_flight) {
if ((ret = derive_exporter_secret(tls, 1)) != 0)
goto Exit;
}
tls->state = cookie == NULL ? PTLS_STATE_CLIENT_EXPECT_SERVER_HELLO : PTLS_STATE_CLIENT_EXPECT_SECOND_SERVER_HELLO;
ret = PTLS_ERROR_IN_PROGRESS;
Exit:
ptls_buffer_dispose(&encoded_ch_inner);
ptls_clear_memory(binder_key, sizeof(binder_key));
return ret;
}
ptls_cipher_suite_t *ptls_find_cipher_suite(ptls_cipher_suite_t **cipher_suites, uint16_t id)
{
ptls_cipher_suite_t **cs;
if (cipher_suites == NULL)
return NULL;
for (cs = cipher_suites; *cs != NULL && (*cs)->id != id; ++cs)
;
return *cs;
}
static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, const uint8_t *src, const uint8_t *const end)
{
int ret;
*sh = (struct st_ptls_server_hello_t){{0}};
/* ignore legacy-version */
if (end - src < 2) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
src += 2;
/* random */
if (end - src < PTLS_HELLO_RANDOM_SIZE) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
sh->is_retry_request = memcmp(src, hello_retry_random, PTLS_HELLO_RANDOM_SIZE) == 0;
src += PTLS_HELLO_RANDOM_SIZE;
/* legacy_session_id */
ptls_decode_open_block(src, end, 1, {
if (end - src > 32) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
sh->legacy_session_id = ptls_iovec_init(src, end - src);
src = end;
});
{ /* select cipher_suite */
uint16_t csid;
if ((ret = ptls_decode16(&csid, &src, end)) != 0)
goto Exit;
if ((tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid)) == NULL) {
ret = PTLS_ALERT_ILLEGAL_PARAMETER;
goto Exit;
}
}
{ /* legacy_compression_method */
uint8_t method;
if ((ret = ptls_decode8(&method, &src, end)) != 0)
goto Exit;
if (method != 0) {
ret = PTLS_ALERT_ILLEGAL_PARAMETER;
goto Exit;
}
}
if (sh->is_retry_request)
sh->retry_request.selected_group = UINT16_MAX;
uint16_t exttype, found_version = UINT16_MAX, selected_psk_identity = UINT16_MAX;
decode_extensions(src, end, sh->is_retry_request ? PTLS_HANDSHAKE_TYPE_PSEUDO_HRR : PTLS_HANDSHAKE_TYPE_SERVER_HELLO, &exttype,
{
if (tls->ctx->on_extension != NULL &&
(ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_SERVER_HELLO,
exttype, ptls_iovec_init(src, end - src)) != 0))
goto Exit;
switch (exttype) {
case PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS:
if ((ret = ptls_decode16(&found_version, &src, end)) != 0)
goto Exit;
break;
case PTLS_EXTENSION_TYPE_KEY_SHARE:
if (sh->is_retry_request) {
if ((ret = ptls_decode16(&sh->retry_request.selected_group, &src, end)) != 0)
goto Exit;
} else {
uint16_t group;
if ((ret = decode_key_share_entry(&group, &sh->peerkey,