diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
commit | be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97 (patch) | |
tree | 9754ff1ca740f6346cf8483ec915d4054bc5da2d /web/server/h2o/libh2o/deps/picotls/lib | |
parent | Initial commit. (diff) | |
download | netdata-upstream.tar.xz netdata-upstream.zip |
Adding upstream version 1.44.3.upstream/1.44.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'web/server/h2o/libh2o/deps/picotls/lib')
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/asn1.c | 295 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/cifra.c | 530 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/minicrypto-pem.c | 339 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/openssl.c | 1066 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/pembase64.c | 373 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/picotls.c | 3761 | ||||
-rw-r--r-- | web/server/h2o/libh2o/deps/picotls/lib/uecc.c | 190 |
7 files changed, 6554 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/deps/picotls/lib/asn1.c b/web/server/h2o/libh2o/deps/picotls/lib/asn1.c new file mode 100644 index 00000000..4422bed9 --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/asn1.c @@ -0,0 +1,295 @@ +/* +* Copyright (c) 2016 Christian Huitema <huitema@huitema.net> +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* +* Basic ASN1 validation and optional print-out +*/ + +#ifdef _WINDOWS +#include "wincompat.h" +#else +#include <sys/time.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "picotls.h" +#include "picotls/minicrypto.h" +#include "picotls/asn1.h" + +static char const *asn1_type_classes[4] = {"Universal", "Application", "Context-specific", "Private"}; + +static char const *asn1_universal_types[] = { + "End-of-Content", "BOOLEAN", "INTEGER", "BIT STRING", "OCTET STRING", "NULL", + "OBJECT IDENTIFIER", "Object Descriptor", "EXTERNAL", "REAL", "ENUMERATED", "EMBEDDED PDV", + "UTF8String", "RELATIVE-OID", "Reserved (16)", "Reserved (17)", "SEQUENCE", "SET", + "NumericString", "PrintableString", "T61String", "VideotexString", "IA5String", "UTCTime", + "GeneralizedTime", "GraphicString", "VisibleString", "GeneralString", "UniversalString", "CHARACTER STRING", + "BMPString"}; + +static size_t nb_asn1_universal_types = sizeof(asn1_universal_types) / sizeof(char const *); + +static void ptls_asn1_print_indent(int level, ptls_minicrypto_log_ctx_t *log_ctx) +{ + for (int indent = 0; indent <= level; indent++) { + log_ctx->fn(log_ctx->ctx, " "); + } +} + +size_t ptls_asn1_error_message(char const *error_label, size_t bytes_max, size_t byte_index, int level, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + if (log_ctx != NULL) { + ptls_asn1_print_indent(level, log_ctx); + log_ctx->fn(log_ctx->ctx, "Error: %s (near position: %d (0x%x) out of %d)", error_label, (int)byte_index, + (uint32_t)byte_index, (int)bytes_max); + } + return byte_index; +} + +void ptls_asn1_dump_content(const uint8_t *bytes, size_t bytes_max, size_t byte_index, ptls_minicrypto_log_ctx_t *log_ctx) +{ + if (log_ctx != NULL && bytes_max > byte_index) { + size_t nb_bytes = bytes_max - byte_index; + + log_ctx->fn(log_ctx->ctx, " "); + + for (size_t i = 0; i < 16 && i < nb_bytes; i++) { + log_ctx->fn(log_ctx->ctx, "%02x", bytes[byte_index + i]); + } + + if (nb_bytes > 16) { + log_ctx->fn(log_ctx->ctx, "..."); + } + } +} + +size_t ptls_asn1_read_type(const uint8_t *bytes, size_t bytes_max, int *structure_bit, int *type_class, uint32_t *type_number, + int *decode_error, int level, ptls_minicrypto_log_ctx_t *log_ctx) +{ + /* Get the type byte */ + size_t byte_index = 1; + uint8_t first_byte = bytes[0]; + *structure_bit = (first_byte >> 5) & 1; + *type_class = (first_byte >> 6) & 3; + *type_number = first_byte & 31; + + if (*type_number == 31) { + uint32_t long_type = 0; + const uint32_t type_number_limit = 0x07FFFFFFF; + int next_byte; + int end_found = 0; + + while (byte_index < bytes_max && long_type <= type_number_limit) { + next_byte = bytes[byte_index++]; + long_type <<= 7; + long_type |= next_byte & 127; + if ((next_byte & 128) == 0) { + end_found = 1; + break; + } + } + + if (end_found) { + *type_number = long_type; + } else { + /* This is an error */ + byte_index = ptls_asn1_error_message("Incorrect type coding", bytes_max, byte_index, level, log_ctx); + *decode_error = PTLS_ERROR_BER_MALFORMED_TYPE; + } + } + + return byte_index; +} + +void ptls_asn1_print_type(int type_class, uint32_t type_number, int level, ptls_minicrypto_log_ctx_t *log_ctx) +{ + /* Print the type */ + ptls_asn1_print_indent(level, log_ctx); + if (type_class == 0 && type_number < nb_asn1_universal_types) { + log_ctx->fn(log_ctx->ctx, "%s", asn1_universal_types[type_number]); + } else if (type_class == 2) { + log_ctx->fn(log_ctx->ctx, "[%d]", type_number); + } else { + log_ctx->fn(log_ctx->ctx, "%s[%d]", asn1_type_classes[type_class], type_number); + } +} + +size_t ptls_asn1_read_length(const uint8_t *bytes, size_t bytes_max, size_t byte_index, uint32_t *length, int *indefinite_length, + size_t *last_byte, int *decode_error, int level, ptls_minicrypto_log_ctx_t *log_ctx) +{ + int length_of_length = 0; + + *indefinite_length = 0; + *length = 0; + *last_byte = bytes_max; + + if (byte_index < bytes_max) { + *length = bytes[byte_index++]; + if ((*length & 128) != 0) { + length_of_length = *length & 127; + *length = 0; + + if (byte_index + length_of_length >= bytes_max) { + /* This is an error */ + byte_index = ptls_asn1_error_message("Incorrect length coding", bytes_max, byte_index, level, log_ctx); + *decode_error = PTLS_ERROR_BER_MALFORMED_LENGTH; + } else { + for (int i = 0; i < length_of_length && byte_index < bytes_max; i++) { + *length <<= 8; + *length |= bytes[byte_index++]; + } + + if (length_of_length == 0) { + *last_byte = bytes_max; + *indefinite_length = 1; + } else { + *last_byte = byte_index + *length; + } + } + } else { + *last_byte = byte_index + *length; + } + + if (*decode_error == 0) { + /* TODO: verify that the length makes sense */ + if (*last_byte > bytes_max) { + byte_index = ptls_asn1_error_message("Length larger than message", bytes_max, byte_index, level, log_ctx); + *decode_error = PTLS_ERROR_BER_EXCESSIVE_LENGTH; + } + } + } + + return byte_index; +} + +size_t ptls_asn1_get_expected_type_and_length(const uint8_t *bytes, size_t bytes_max, size_t byte_index, uint8_t expected_type, + uint32_t *length, int *indefinite_length, size_t *last_byte, int *decode_error, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + int is_indefinite = 0; + + /* Check that the expected type is present */ + if (bytes[byte_index] != expected_type) { + byte_index = ptls_asn1_error_message("Unexpected type", bytes_max, byte_index, 0, log_ctx); + *decode_error = PTLS_ERROR_INCORRECT_ASN1_SYNTAX; + } else { + /* get length of element */ + byte_index++; + byte_index = + ptls_asn1_read_length(bytes, bytes_max, byte_index, length, &is_indefinite, last_byte, decode_error, 0, log_ctx); + + if (indefinite_length != NULL) { + *indefinite_length = is_indefinite; + } else if (is_indefinite) { + byte_index = ptls_asn1_error_message("Incorrect length for DER", bytes_max, byte_index, 0, log_ctx); + *decode_error = PTLS_ERROR_DER_INDEFINITE_LENGTH; + } + } + + return byte_index; +} + +size_t ptls_asn1_validation_recursive(const uint8_t *bytes, size_t bytes_max, int *decode_error, int level, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + /* Get the type byte */ + int structure_bit = 0; + int type_class = 0; + uint32_t type_number = 0; + uint32_t length = 0; + int indefinite_length = 0; + size_t last_byte = 0; + /* Decode the type */ + size_t byte_index = + ptls_asn1_read_type(bytes, bytes_max, &structure_bit, &type_class, &type_number, decode_error, level, log_ctx); + + if (*decode_error == 0 && log_ctx != NULL) { + ptls_asn1_print_type(type_class, type_number, level, log_ctx); + } + + /* Get the length */ + byte_index = + ptls_asn1_read_length(bytes, bytes_max, byte_index, &length, &indefinite_length, &last_byte, decode_error, level, log_ctx); + + if (last_byte <= bytes_max) { + if (structure_bit) { + /* If structured, recurse on a loop */ + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, " {\n"); + } + + while (byte_index < last_byte) { + if (indefinite_length != 0 && bytes[byte_index] == 0) { + if (byte_index + 2 > bytes_max || bytes[byte_index + 1] != 0) { + byte_index = + ptls_asn1_error_message("EOC: unexpected end of content", bytes_max, byte_index, level + 1, log_ctx); + + *decode_error = PTLS_ERROR_BER_UNEXPECTED_EOC; + } else { + if (log_ctx != NULL) { + ptls_asn1_print_indent(level, log_ctx); + log_ctx->fn(log_ctx->ctx, "EOC\n"); + } + byte_index += 2; + break; + } + } else { + byte_index += ptls_asn1_validation_recursive(bytes + byte_index, last_byte - byte_index, decode_error, + level + 1, log_ctx); + + if (*decode_error) { + byte_index = bytes_max; + break; + } + } + + if (log_ctx != NULL) { + if (byte_index < last_byte) { + log_ctx->fn(log_ctx->ctx, ","); + } + log_ctx->fn(log_ctx->ctx, "\n"); + } + } + + if (log_ctx != NULL) { + ptls_asn1_print_indent(level, log_ctx); + log_ctx->fn(log_ctx->ctx, "}"); + } + } else { + ptls_asn1_dump_content(bytes, last_byte, byte_index, log_ctx); + byte_index = last_byte; + } + } + + return byte_index; +} + +int ptls_asn1_validation(const uint8_t *bytes, size_t length, ptls_minicrypto_log_ctx_t *log_ctx) +{ + int decode_error = 0; + size_t decoded = ptls_asn1_validation_recursive(bytes, length, &decode_error, 0, log_ctx); + + if (decode_error == 0 && decoded < length) { + decode_error = PTLS_ERROR_BER_ELEMENT_TOO_SHORT; + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "Type too short, %d bytes only out of %d\n", (int)decoded, (int)length); + } + } + + return decode_error; +} diff --git a/web/server/h2o/libh2o/deps/picotls/lib/cifra.c b/web/server/h2o/libh2o/deps/picotls/lib/cifra.c new file mode 100644 index 00000000..af33c33f --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/cifra.c @@ -0,0 +1,530 @@ +/* + * 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. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef _WINDOWS +#include "wincompat.h" +#else +#include <unistd.h> +#endif +#include "aes.h" +#include "bitops.h" +#include "drbg.h" +#include "curve25519.h" +#include "../deps/cifra/src/ext/handy.h" +#include "modes.h" +#include "poly1305.h" +#include "salsa20.h" +#include "sha2.h" +#include "picotls.h" +#include "picotls/minicrypto.h" + +#ifdef _WINDOWS +#include <wincrypt.h> +static void read_entropy(uint8_t *entropy, size_t size) +{ + HCRYPTPROV hCryptProv = 0; + BOOL ret = FALSE; + + if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0)) { + ret = CryptGenRandom(hCryptProv, (DWORD)size, entropy); + (void)CryptReleaseContext(hCryptProv, 0); + } + + if (ret == FALSE) { + abort(); + } +} +#else +static void read_entropy(uint8_t *entropy, size_t size) +{ + int fd; + + if ((fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC)) == -1) { + if ((fd = open("/dev/random", O_RDONLY | O_CLOEXEC)) == -1) { + perror("ptls_minicrypto_random_bytes: could not open neither /dev/random or /dev/urandom"); + abort(); + } + } + + while (size != 0) { + ssize_t rret; + while ((rret = read(fd, entropy, size)) == -1 && errno == EINTR) + ; + if (rret < 0) { + perror("ptls_minicrypto_random_bytes"); + abort(); + } + entropy += rret; + size -= rret; + } + + close(fd); +} +#endif + +void ptls_minicrypto_random_bytes(void *buf, size_t len) +{ +#ifdef _WINDOWS + static __declspec(thread) cf_hash_drbg_sha256 ctx; +#else + static __thread cf_hash_drbg_sha256 ctx; +#endif + + if (cf_hash_drbg_sha256_needs_reseed(&ctx)) { + uint8_t entropy[256]; + read_entropy(entropy, sizeof(entropy)); + cf_hash_drbg_sha256_init(&ctx, entropy, sizeof(entropy) / 2, entropy + sizeof(entropy) / 2, sizeof(entropy) / 2, "ptls", 4); + } + cf_hash_drbg_sha256_gen(&ctx, buf, len); +} + +#define X25519_KEY_SIZE 32 + +struct st_x25519_key_exchange_t { + ptls_key_exchange_context_t super; + uint8_t priv[X25519_KEY_SIZE]; + uint8_t pub[X25519_KEY_SIZE]; +}; + +static void x25519_create_keypair(uint8_t *priv, uint8_t *pub) +{ + ptls_minicrypto_random_bytes(priv, X25519_KEY_SIZE); + cf_curve25519_mul_base(pub, priv); +} + +static int x25519_derive_secret(ptls_iovec_t *secret, const uint8_t *clientpriv, const uint8_t *clientpub, + const uint8_t *serverpriv, const uint8_t *serverpub) +{ + if ((secret->base = malloc(X25519_KEY_SIZE)) == NULL) + return PTLS_ERROR_NO_MEMORY; + + cf_curve25519_mul(secret->base, clientpriv != NULL ? clientpriv : serverpriv, clientpriv != NULL ? serverpub : clientpub); + secret->len = X25519_KEY_SIZE; + return 0; +} + +static int x25519_on_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + struct st_x25519_key_exchange_t *ctx = (struct st_x25519_key_exchange_t *)*_ctx; + int ret; + + *_ctx = NULL; + + if (secret == NULL) { + ret = 0; + goto Exit; + } + + if (peerkey.len != X25519_KEY_SIZE) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + ret = x25519_derive_secret(secret, ctx->priv, ctx->pub, NULL, peerkey.base); + +Exit: + ptls_clear_memory(ctx->priv, sizeof(ctx->priv)); + free(ctx); + return ret; +} + +static int x25519_create_key_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *pubkey) +{ + struct st_x25519_key_exchange_t *ctx; + + if ((ctx = (struct st_x25519_key_exchange_t *)malloc(sizeof(*ctx))) == NULL) + return PTLS_ERROR_NO_MEMORY; + ctx->super = (ptls_key_exchange_context_t){x25519_on_exchange}; + x25519_create_keypair(ctx->priv, ctx->pub); + + *_ctx = &ctx->super; + *pubkey = ptls_iovec_init(ctx->pub, sizeof(ctx->pub)); + return 0; +} + +static int x25519_key_exchange(ptls_iovec_t *pubkey, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + uint8_t priv[X25519_KEY_SIZE], *pub = NULL; + int ret; + + if (peerkey.len != X25519_KEY_SIZE) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + if ((pub = malloc(X25519_KEY_SIZE)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + x25519_create_keypair(priv, pub); + if ((ret = x25519_derive_secret(secret, NULL, peerkey.base, priv, pub)) != 0) + goto Exit; + + *pubkey = ptls_iovec_init(pub, X25519_KEY_SIZE); + ret = 0; + +Exit: + ptls_clear_memory(priv, sizeof(priv)); + if (pub != NULL && ret != 0) + ptls_clear_memory(pub, X25519_KEY_SIZE); + return ret; +} + +struct aesctr_context_t { + ptls_cipher_context_t super; + cf_aes_context aes; + cf_ctr ctr; +}; + +static void aesctr_dispose(ptls_cipher_context_t *_ctx) +{ + struct aesctr_context_t *ctx = (struct aesctr_context_t *)_ctx; + ptls_clear_memory(ctx, sizeof(*ctx)); +} + +static void aesctr_init(ptls_cipher_context_t *_ctx, const void *iv) +{ + struct aesctr_context_t *ctx = (struct aesctr_context_t *)_ctx; + cf_ctr_init(&ctx->ctr, &cf_aes, &ctx->aes, iv); +} + +static void aesctr_transform(ptls_cipher_context_t *_ctx, void *output, const void *input, size_t len) +{ + struct aesctr_context_t *ctx = (struct aesctr_context_t *)_ctx; + cf_ctr_cipher(&ctx->ctr, input, output, len); +} + +static int aesctr_setup_crypto(ptls_cipher_context_t *_ctx, int is_enc, const void *key, size_t key_size) +{ + struct aesctr_context_t *ctx = (struct aesctr_context_t *)_ctx; + ctx->super.do_dispose = aesctr_dispose; + ctx->super.do_init = aesctr_init; + ctx->super.do_transform = aesctr_transform; + cf_aes_init(&ctx->aes, key, key_size); + return 0; +} + +static int aes128ctr_setup_crypto(ptls_cipher_context_t *ctx, int is_enc, const void *key) +{ + return aesctr_setup_crypto(ctx, is_enc, key, PTLS_AES128_KEY_SIZE); +} + +static int aes256ctr_setup_crypto(ptls_cipher_context_t *ctx, int is_enc, const void *key) +{ + return aesctr_setup_crypto(ctx, is_enc, key, PTLS_AES256_KEY_SIZE); +} + +struct aesgcm_context_t { + ptls_aead_context_t super; + cf_aes_context aes; + cf_gcm_ctx gcm; +}; + +static void aesgcm_dispose_crypto(ptls_aead_context_t *_ctx) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + /* clear all memory except super */ + ptls_clear_memory((uint8_t *)ctx + sizeof(ctx->super), sizeof(*ctx) - sizeof(ctx->super)); +} + +static void aesgcm_encrypt_init(ptls_aead_context_t *_ctx, const void *iv, const void *aad, size_t aadlen) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + cf_gcm_encrypt_init(&cf_aes, &ctx->aes, &ctx->gcm, aad, aadlen, iv, PTLS_AESGCM_IV_SIZE); +} + +static size_t aesgcm_encrypt_update(ptls_aead_context_t *_ctx, void *output, const void *input, size_t inlen) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + cf_gcm_encrypt_update(&ctx->gcm, input, inlen, output); + return inlen; +} + +static size_t aesgcm_encrypt_final(ptls_aead_context_t *_ctx, void *output) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + cf_gcm_encrypt_final(&ctx->gcm, output, PTLS_AESGCM_TAG_SIZE); + return PTLS_AESGCM_TAG_SIZE; +} + +static size_t aesgcm_decrypt(ptls_aead_context_t *_ctx, void *output, const void *input, size_t inlen, const void *iv, + const void *aad, size_t aadlen) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + if (inlen < PTLS_AESGCM_TAG_SIZE) + return SIZE_MAX; + size_t tag_offset = inlen - PTLS_AESGCM_TAG_SIZE; + + if (cf_gcm_decrypt(&cf_aes, &ctx->aes, input, tag_offset, aad, aadlen, iv, PTLS_AESGCM_IV_SIZE, (uint8_t *)input + tag_offset, + PTLS_AESGCM_TAG_SIZE, output) != 0) + return SIZE_MAX; + + return tag_offset; +} + +static int aead_aesgcm_setup_crypto(ptls_aead_context_t *_ctx, int is_enc, const void *key, size_t key_size) +{ + struct aesgcm_context_t *ctx = (struct aesgcm_context_t *)_ctx; + + ctx->super.dispose_crypto = aesgcm_dispose_crypto; + if (is_enc) { + ctx->super.do_encrypt_init = aesgcm_encrypt_init; + ctx->super.do_encrypt_update = aesgcm_encrypt_update; + ctx->super.do_encrypt_final = aesgcm_encrypt_final; + ctx->super.do_decrypt = NULL; + } else { + ctx->super.do_encrypt_init = NULL; + ctx->super.do_encrypt_update = NULL; + ctx->super.do_encrypt_final = NULL; + ctx->super.do_decrypt = aesgcm_decrypt; + } + + cf_aes_init(&ctx->aes, key, key_size); + return 0; +} + +static int aead_aes128gcm_setup_crypto(ptls_aead_context_t *ctx, int is_enc, const void *key) +{ + return aead_aesgcm_setup_crypto(ctx, is_enc, key, PTLS_AES128_KEY_SIZE); +} + +static int aead_aes256gcm_setup_crypto(ptls_aead_context_t *ctx, int is_enc, const void *key) +{ + return aead_aesgcm_setup_crypto(ctx, is_enc, key, PTLS_AES256_KEY_SIZE); +} + +struct chacha20_context_t { + ptls_cipher_context_t super; + cf_chacha20_ctx chacha; + uint8_t key[PTLS_CHACHA20_KEY_SIZE]; +}; + +static void chacha20_dispose(ptls_cipher_context_t *_ctx) +{ + struct chacha20_context_t *ctx = (struct chacha20_context_t *)_ctx; + ptls_clear_memory(ctx, sizeof(*ctx)); +} + +static void chacha20_init(ptls_cipher_context_t *_ctx, const void *iv) +{ + struct chacha20_context_t *ctx = (struct chacha20_context_t *)_ctx; + ctx->chacha.nblock = 0; + ctx->chacha.ncounter = 0; + memcpy(ctx->chacha.nonce, iv, sizeof ctx->chacha.nonce); +} + +static void chacha20_transform(ptls_cipher_context_t *_ctx, void *output, const void *input, size_t len) +{ + struct chacha20_context_t *ctx = (struct chacha20_context_t *)_ctx; + cf_chacha20_cipher(&ctx->chacha, input, output, len); +} + +static int chacha20_setup_crypto(ptls_cipher_context_t *_ctx, int is_enc, const void *key) +{ + struct chacha20_context_t *ctx = (struct chacha20_context_t *)_ctx; + ctx->super.do_dispose = chacha20_dispose; + ctx->super.do_init = chacha20_init; + ctx->super.do_transform = chacha20_transform; + cf_chacha20_init(&ctx->chacha, key, PTLS_CHACHA20_KEY_SIZE, (const uint8_t *)"01234567" /* not used */); + return 0; +} + +struct chacha20poly1305_context_t { + ptls_aead_context_t super; + uint8_t key[PTLS_CHACHA20_KEY_SIZE]; + cf_chacha20_ctx chacha; + cf_poly1305 poly; + size_t aadlen; + size_t textlen; +}; + +static void chacha20poly1305_dispose_crypto(ptls_aead_context_t *_ctx) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + + /* clear all memory except super */ + ptls_clear_memory(&ctx->key, sizeof(*ctx) - offsetof(struct chacha20poly1305_context_t, key)); +} + +static const uint8_t zeros64[64] = {0}; + +static void chacha20poly1305_encrypt_pad(cf_poly1305 *poly, size_t n) +{ + if (n % 16 != 0) + cf_poly1305_update(poly, zeros64, 16 - (n % 16)); +} + +static void chacha20poly1305_finalize(struct chacha20poly1305_context_t *ctx, uint8_t *tag) +{ + uint8_t lenbuf[16]; + + chacha20poly1305_encrypt_pad(&ctx->poly, ctx->textlen); + + write64_le(ctx->aadlen, lenbuf); + write64_le(ctx->textlen, lenbuf + 8); + cf_poly1305_update(&ctx->poly, lenbuf, sizeof(lenbuf)); + + cf_poly1305_finish(&ctx->poly, tag); +} + +static void chacha20poly1305_init(ptls_aead_context_t *_ctx, const void *iv, const void *aad, size_t aadlen) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + uint8_t tmpbuf[64]; + + /* init chacha */ + memset(tmpbuf, 0, 16 - PTLS_CHACHA20POLY1305_IV_SIZE); + memcpy(tmpbuf + 16 - PTLS_CHACHA20POLY1305_IV_SIZE, iv, PTLS_CHACHA20POLY1305_IV_SIZE); + cf_chacha20_init_custom(&ctx->chacha, ctx->key, sizeof(ctx->key), tmpbuf, 4); + + /* init poly1305 (by using first 16 bytes of the key stream of the first block) */ + cf_chacha20_cipher(&ctx->chacha, zeros64, tmpbuf, 64); + cf_poly1305_init(&ctx->poly, tmpbuf, tmpbuf + 16); + + ptls_clear_memory(tmpbuf, sizeof(tmpbuf)); + + /* aad */ + if (aadlen != 0) { + cf_poly1305_update(&ctx->poly, aad, aadlen); + chacha20poly1305_encrypt_pad(&ctx->poly, aadlen); + } + + ctx->aadlen = aadlen; + ctx->textlen = 0; +} + +static size_t chacha20poly1305_encrypt_update(ptls_aead_context_t *_ctx, void *output, const void *input, size_t inlen) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + + cf_chacha20_cipher(&ctx->chacha, input, output, inlen); + cf_poly1305_update(&ctx->poly, output, inlen); + ctx->textlen += inlen; + + return inlen; +} + +static size_t chacha20poly1305_encrypt_final(ptls_aead_context_t *_ctx, void *output) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + + chacha20poly1305_finalize(ctx, output); + + ptls_clear_memory(&ctx->chacha, sizeof(ctx->chacha)); + return PTLS_CHACHA20POLY1305_TAG_SIZE; +} + +static size_t chacha20poly1305_decrypt(ptls_aead_context_t *_ctx, void *output, const void *input, size_t inlen, const void *iv, + const void *aad, size_t aadlen) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + uint8_t tag[PTLS_CHACHA20POLY1305_TAG_SIZE]; + size_t ret; + + if (inlen < sizeof(tag)) + return SIZE_MAX; + + chacha20poly1305_init(&ctx->super, iv, aad, aadlen); + + cf_poly1305_update(&ctx->poly, input, inlen - sizeof(tag)); + ctx->textlen = inlen - sizeof(tag); + + chacha20poly1305_finalize(ctx, tag); + if (mem_eq(tag, (const uint8_t *)input + inlen - sizeof(tag), sizeof(tag))) { + cf_chacha20_cipher(&ctx->chacha, input, output, inlen - sizeof(tag)); + ret = inlen - sizeof(tag); + } else { + ret = SIZE_MAX; + } + + ptls_clear_memory(tag, sizeof(tag)); + ptls_clear_memory(&ctx->poly, sizeof(ctx->poly)); + + return ret; +} + +static int aead_chacha20poly1305_setup_crypto(ptls_aead_context_t *_ctx, int is_enc, const void *key) +{ + struct chacha20poly1305_context_t *ctx = (struct chacha20poly1305_context_t *)_ctx; + + ctx->super.dispose_crypto = chacha20poly1305_dispose_crypto; + if (is_enc) { + ctx->super.do_encrypt_init = chacha20poly1305_init; + ctx->super.do_encrypt_update = chacha20poly1305_encrypt_update; + ctx->super.do_encrypt_final = chacha20poly1305_encrypt_final; + ctx->super.do_decrypt = NULL; + } else { + ctx->super.do_encrypt_init = NULL; + ctx->super.do_encrypt_update = NULL; + ctx->super.do_encrypt_final = NULL; + ctx->super.do_decrypt = chacha20poly1305_decrypt; + } + + memcpy(ctx->key, key, sizeof(ctx->key)); + return 0; +} + +ptls_define_hash(sha256, cf_sha256_context, cf_sha256_init, cf_sha256_update, cf_sha256_digest_final); +ptls_define_hash(sha384, cf_sha512_context, cf_sha384_init, cf_sha384_update, cf_sha384_digest_final); + +ptls_key_exchange_algorithm_t ptls_minicrypto_x25519 = {PTLS_GROUP_X25519, x25519_create_key_exchange, x25519_key_exchange}; +ptls_cipher_algorithm_t ptls_minicrypto_aes128ctr = {"AES128-CTR", PTLS_AES128_KEY_SIZE, PTLS_AES_IV_SIZE, + sizeof(struct aesctr_context_t), aes128ctr_setup_crypto}; +ptls_aead_algorithm_t ptls_minicrypto_aes128gcm = { + "AES128-GCM", &ptls_minicrypto_aes128ctr, PTLS_AES128_KEY_SIZE, PTLS_AESGCM_IV_SIZE, + PTLS_AESGCM_TAG_SIZE, sizeof(struct aesgcm_context_t), aead_aes128gcm_setup_crypto}; +ptls_cipher_algorithm_t ptls_minicrypto_aes256ctr = {"AES256-CTR", PTLS_AES256_KEY_SIZE, PTLS_AES_IV_SIZE, + sizeof(struct aesctr_context_t), aes256ctr_setup_crypto}; +ptls_aead_algorithm_t ptls_minicrypto_aes256gcm = { + "AES256-GCM", &ptls_minicrypto_aes256ctr, PTLS_AES256_KEY_SIZE, PTLS_AESGCM_IV_SIZE, + PTLS_AESGCM_TAG_SIZE, sizeof(struct aesgcm_context_t), aead_aes256gcm_setup_crypto}; +ptls_hash_algorithm_t ptls_minicrypto_sha256 = {PTLS_SHA256_BLOCK_SIZE, PTLS_SHA256_DIGEST_SIZE, sha256_create, + PTLS_ZERO_DIGEST_SHA256}; +ptls_hash_algorithm_t ptls_minicrypto_sha384 = {PTLS_SHA384_BLOCK_SIZE, PTLS_SHA384_DIGEST_SIZE, sha384_create, + PTLS_ZERO_DIGEST_SHA384}; +ptls_cipher_algorithm_t ptls_minicrypto_chacha20 = {"CHACHA20", PTLS_CHACHA20_KEY_SIZE, PTLS_CHACHA20_IV_SIZE, + sizeof(struct chacha20_context_t), chacha20_setup_crypto}; +ptls_aead_algorithm_t ptls_minicrypto_chacha20poly1305 = {"CHACHA20-POLY1305", + &ptls_minicrypto_chacha20, + PTLS_CHACHA20_KEY_SIZE, + PTLS_CHACHA20POLY1305_IV_SIZE, + PTLS_CHACHA20POLY1305_TAG_SIZE, + sizeof(struct chacha20poly1305_context_t), + aead_chacha20poly1305_setup_crypto}; +ptls_cipher_suite_t ptls_minicrypto_aes128gcmsha256 = {PTLS_CIPHER_SUITE_AES_128_GCM_SHA256, &ptls_minicrypto_aes128gcm, + &ptls_minicrypto_sha256}; +ptls_cipher_suite_t ptls_minicrypto_aes256gcmsha384 = {PTLS_CIPHER_SUITE_AES_256_GCM_SHA384, &ptls_minicrypto_aes256gcm, + &ptls_minicrypto_sha384}; +ptls_cipher_suite_t ptls_minicrypto_chacha20poly1305sha256 = {PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256, + &ptls_minicrypto_chacha20poly1305, &ptls_minicrypto_sha256}; +ptls_cipher_suite_t *ptls_minicrypto_cipher_suites[] = {&ptls_minicrypto_aes256gcmsha384, &ptls_minicrypto_aes128gcmsha256, + &ptls_minicrypto_chacha20poly1305sha256, NULL}; diff --git a/web/server/h2o/libh2o/deps/picotls/lib/minicrypto-pem.c b/web/server/h2o/libh2o/deps/picotls/lib/minicrypto-pem.c new file mode 100644 index 00000000..2b82a91e --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/minicrypto-pem.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2017,2018 Christian Huitema + * + * 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 <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef _WINDOWS +#include "wincompat.h" +#else +#include <unistd.h> +#endif +#include "picotls.h" +#include "picotls/minicrypto.h" +#include "picotls/asn1.h" +#include "picotls/pembase64.h" + + +/* + * This function could be declared as static, but we want to access it + * in the unit tests. + */ +size_t ptls_minicrypto_asn1_decode_private_key(ptls_asn1_pkcs8_private_key_t *pkey, int *decode_error, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + uint8_t *bytes = pkey->vec.base; + size_t bytes_max = pkey->vec.len; + + /* read the ASN1 messages */ + size_t byte_index = 0; + uint32_t seq0_length = 0; + size_t last_byte0; + uint32_t seq1_length = 0; + size_t last_byte1 = 0; + uint32_t oid_length; + size_t last_oid_byte; + uint32_t key_data_length; + size_t key_data_last; + + /* start with sequence */ + byte_index = ptls_asn1_get_expected_type_and_length(bytes, bytes_max, byte_index, 0x30, &seq0_length, NULL, &last_byte0, + decode_error, log_ctx); + + if (*decode_error == 0 && bytes_max != last_byte0) { + byte_index = ptls_asn1_error_message("Length larger than message", bytes_max, byte_index, 0, log_ctx); + *decode_error = PTLS_ERROR_BER_EXCESSIVE_LENGTH; + } + + if (*decode_error == 0) { + /* get first component: version, INTEGER, expect value 0 */ + if (byte_index + 3 > bytes_max) { + byte_index = ptls_asn1_error_message("Cannot find key version", bytes_max, byte_index, 0, log_ctx); + *decode_error = PTLS_ERROR_INCORRECT_PEM_KEY_VERSION; + } else if (bytes[byte_index] != 0x02 || bytes[byte_index + 1] != 0x01 || bytes[byte_index + 2] != 0x00) { + *decode_error = PTLS_ERROR_INCORRECT_PEM_KEY_VERSION; + byte_index = ptls_asn1_error_message("Incorrect PEM Version", bytes_max, byte_index, 0, log_ctx); + } else { + byte_index += 3; + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, " Version = 1,\n"); + } + } + } + + if (*decode_error == 0) { + /* open embedded sequence */ + byte_index = ptls_asn1_get_expected_type_and_length(bytes, bytes_max, byte_index, 0x30, &seq1_length, NULL, &last_byte1, + decode_error, log_ctx); + } + + if (*decode_error == 0) { + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, " Algorithm Identifier:\n"); + } + /* get length of OID */ + byte_index = ptls_asn1_get_expected_type_and_length(bytes, last_byte1, byte_index, 0x06, &oid_length, NULL, &last_oid_byte, + decode_error, log_ctx); + + if (*decode_error == 0) { + if (log_ctx != NULL) { + /* print the OID value */ + log_ctx->fn(log_ctx->ctx, " Algorithm:"); + ptls_asn1_dump_content(bytes + byte_index, oid_length, 0, log_ctx); + log_ctx->fn(log_ctx->ctx, ",\n"); + } + pkey->algorithm_index = byte_index; + pkey->algorithm_length = oid_length; + byte_index += oid_length; + } + } + + if (*decode_error == 0) { + /* get parameters, ANY */ + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, " Parameters:\n"); + } + + pkey->parameters_index = byte_index; + + pkey->parameters_length = + ptls_asn1_validation_recursive(bytes + byte_index, last_byte1 - byte_index, decode_error, 2, log_ctx); + + byte_index += pkey->parameters_length; + + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "\n"); + } + /* close sequence */ + if (byte_index != last_byte1) { + byte_index = ptls_asn1_error_message("Length larger than element", bytes_max, byte_index, 2, log_ctx); + *decode_error = PTLS_ERROR_BER_ELEMENT_TOO_SHORT; + } + } + + /* get octet string, key */ + if (*decode_error == 0) { + byte_index = ptls_asn1_get_expected_type_and_length(bytes, last_byte0, byte_index, 0x04, &key_data_length, NULL, + &key_data_last, decode_error, log_ctx); + + if (*decode_error == 0) { + pkey->key_data_index = byte_index; + pkey->key_data_length = key_data_length; + byte_index += key_data_length; + + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, " Key data (%d bytes):\n", key_data_length); + + (void)ptls_asn1_validation_recursive(bytes + pkey->key_data_index, key_data_length, decode_error, 1, log_ctx); + log_ctx->fn(log_ctx->ctx, "\n"); + } + } + } + + if (*decode_error == 0 && byte_index != last_byte0) { + byte_index = ptls_asn1_error_message("Length larger than element", bytes_max, byte_index, 0, log_ctx); + *decode_error = PTLS_ERROR_BER_ELEMENT_TOO_SHORT; + } + + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "\n"); + } + + return byte_index; +} + +static int ptls_pem_parse_private_key(char const *pem_fname, ptls_asn1_pkcs8_private_key_t *pkey, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + size_t nb_keys = 0; + int ret = ptls_load_pem_objects(pem_fname, "PRIVATE KEY", &pkey->vec, 1, &nb_keys); + + if (ret == 0) { + if (nb_keys != 1) { + ret = PTLS_ERROR_PEM_LABEL_NOT_FOUND; + } + } + + if (ret == 0 && nb_keys == 1) { + int decode_error = 0; + + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "\nFound PRIVATE KEY, length = %d bytes\n", (int)pkey->vec.len); + } + + (void)ptls_minicrypto_asn1_decode_private_key(pkey, &decode_error, log_ctx); + + if (decode_error != 0) { + ret = decode_error; + } + } + + return ret; +} + +static const uint8_t ptls_asn1_algorithm_ecdsa[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01}; + +static const uint8_t ptls_asn1_curve_secp256r1[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}; + +static int ptls_set_ecdsa_private_key(ptls_context_t *ctx, ptls_asn1_pkcs8_private_key_t *pkey, ptls_minicrypto_log_ctx_t *log_ctx) +{ + uint8_t *bytes = pkey->vec.base + pkey->parameters_index; + size_t bytes_max = pkey->parameters_length; + size_t byte_index = 0; + uint8_t *curve_id = NULL; + uint32_t curve_id_length = 0; + int decode_error = 0; + uint32_t seq_length; + size_t last_byte = 0; + uint8_t *ecdsa_key_data = NULL; + uint32_t ecdsa_key_data_length = 0; + size_t ecdsa_key_data_last = 0; + + /* We expect the parameters to include just the curve ID */ + + byte_index = ptls_asn1_get_expected_type_and_length(bytes, bytes_max, byte_index, 0x06, &curve_id_length, NULL, &last_byte, + &decode_error, log_ctx); + + if (decode_error == 0 && bytes_max != last_byte) { + byte_index = ptls_asn1_error_message("Length larger than parameters", bytes_max, byte_index, 0, log_ctx); + decode_error = PTLS_ERROR_BER_EXCESSIVE_LENGTH; + } + + if (decode_error == 0) { + curve_id = bytes + byte_index; + + if (log_ctx != NULL) { + /* print the OID value */ + log_ctx->fn(log_ctx->ctx, "Curve: "); + ptls_asn1_dump_content(curve_id, curve_id_length, 0, log_ctx); + log_ctx->fn(log_ctx->ctx, "\n"); + } + } + + /* We expect the key data to follow the ECDSA structure per RFC 5915 */ + bytes = pkey->vec.base + pkey->key_data_index; + bytes_max = pkey->key_data_length; + byte_index = 0; + + /* decode the wrapping sequence */ + if (decode_error == 0) { + byte_index = ptls_asn1_get_expected_type_and_length(bytes, bytes_max, byte_index, 0x30, &seq_length, NULL, &last_byte, + &decode_error, log_ctx); + } + + if (decode_error == 0 && bytes_max != last_byte) { + byte_index = ptls_asn1_error_message("Length larger than key data", bytes_max, byte_index, 0, log_ctx); + decode_error = PTLS_ERROR_BER_ELEMENT_TOO_SHORT; + } + + /* verify and skip the version number 1 */ + if (decode_error == 0) { + /* get first component: version, INTEGER, expect value 0 */ + if (byte_index + 3 > bytes_max) { + byte_index = ptls_asn1_error_message("Cannot find ECDSA Key Data Version", bytes_max, byte_index, 0, log_ctx); + decode_error = PTLS_ERROR_INCORRECT_ASN1_ECDSA_KEY_SYNTAX; + } else if (bytes[byte_index] != 0x02 || bytes[byte_index + 1] != 0x01 || bytes[byte_index + 2] != 0x01) { + decode_error = PTLS_ERROR_INCORRECT_PEM_ECDSA_KEY_VERSION; + byte_index = ptls_asn1_error_message("Incorrect ECDSA Key Data Version", bytes_max, byte_index, 0, log_ctx); + } else { + byte_index += 3; + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "ECDSA Version = 1,\n"); + } + } + } + + /* obtain the octet string that contains the ECDSA private key */ + if (decode_error == 0) { + byte_index = ptls_asn1_get_expected_type_and_length(bytes, last_byte, byte_index, 0x04, &ecdsa_key_data_length, NULL, + &ecdsa_key_data_last, &decode_error, log_ctx); + + if (decode_error == 0) { + ecdsa_key_data = bytes + byte_index; + } + } + + /* If everything is fine, associate the ECDSA key with the context */ + if (curve_id_length == sizeof(ptls_asn1_curve_secp256r1) && curve_id != NULL && + memcmp(curve_id, ptls_asn1_curve_secp256r1, sizeof(ptls_asn1_curve_secp256r1)) == 0) { + if (SECP256R1_PRIVATE_KEY_SIZE != ecdsa_key_data_length) { + decode_error = PTLS_ERROR_INCORRECT_PEM_ECDSA_KEYSIZE; + if (log_ctx != NULL) { + /* print the OID value */ + log_ctx->fn(log_ctx->ctx, "Wrong SECP256R1 key length, %d instead of %d.\n", ecdsa_key_data_length, + SECP256R1_PRIVATE_KEY_SIZE); + } + } else { + ptls_minicrypto_secp256r1sha256_sign_certificate_t *minicrypto_sign_certificate; + + minicrypto_sign_certificate = (ptls_minicrypto_secp256r1sha256_sign_certificate_t *)malloc( + sizeof(ptls_minicrypto_secp256r1sha256_sign_certificate_t)); + + if (minicrypto_sign_certificate == NULL) { + decode_error = PTLS_ERROR_NO_MEMORY; + } else { + memset(minicrypto_sign_certificate, 0, sizeof(ptls_minicrypto_secp256r1sha256_sign_certificate_t)); + decode_error = ptls_minicrypto_init_secp256r1sha256_sign_certificate( + minicrypto_sign_certificate, ptls_iovec_init(ecdsa_key_data, ecdsa_key_data_length)); + } + if (decode_error == 0) { + ctx->sign_certificate = &minicrypto_sign_certificate->super; + + if (log_ctx != NULL) { + /* print the OID value */ + log_ctx->fn(log_ctx->ctx, "Initialized SECP512R1 signing key with %d bytes.\n", ecdsa_key_data_length); + } + } else if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "SECP512R1 init with %d bytes returns %d.\n", ecdsa_key_data_length, decode_error); + } + } + } else { + decode_error = PTLS_ERROR_INCORRECT_PEM_ECDSA_CURVE; + if (log_ctx != NULL) { + /* print the OID value */ + log_ctx->fn(log_ctx->ctx, "Curve is not supported for signatures.\n"); + } + } + + return decode_error; +} + +int ptls_minicrypto_load_private_key(ptls_context_t *ctx, char const *pem_fname) +{ + ptls_asn1_pkcs8_private_key_t pkey = {{0}}; + int ret = ptls_pem_parse_private_key(pem_fname, &pkey, NULL); + + /* Check that this is the expected key type. + * At this point, the minicrypto library only supports ECDSA keys. + * In theory, we could add support for RSA keys at some point. + */ + if (ret == 0) { + if (pkey.algorithm_length == sizeof(ptls_asn1_algorithm_ecdsa) && + memcmp(pkey.vec.base + pkey.algorithm_index, ptls_asn1_algorithm_ecdsa, sizeof(ptls_asn1_algorithm_ecdsa)) == 0) { + ret = ptls_set_ecdsa_private_key(ctx, &pkey, NULL); + } else { + ret = -1; + } + } + + return ret; +} diff --git a/web/server/h2o/libh2o/deps/picotls/lib/openssl.c b/web/server/h2o/libh2o/deps/picotls/lib/openssl.c new file mode 100644 index 00000000..2c9ee700 --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/openssl.c @@ -0,0 +1,1066 @@ +/* + * 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" +#else +#include <unistd.h> +#endif +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <openssl/bn.h> +#include <openssl/crypto.h> +#include <openssl/ec.h> +#include <openssl/ecdh.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/rand.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> +#include "picotls.h" +#include "picotls/openssl.h" + +#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OPENSSL_1_1_API 1 +#elif defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x2070000fL +#define OPENSSL_1_1_API 1 +#else +#define OPENSSL_1_1_API 0 +#endif + +#if !OPENSSL_1_1_API + +#define EVP_PKEY_up_ref(p) CRYPTO_add(&(p)->references, 1, CRYPTO_LOCK_EVP_PKEY) +#define X509_STORE_up_ref(p) CRYPTO_add(&(p)->references, 1, CRYPTO_LOCK_X509_STORE) + +static HMAC_CTX *HMAC_CTX_new(void) +{ + HMAC_CTX *ctx; + + if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL) + return NULL; + HMAC_CTX_init(ctx); + return ctx; +} + +static void HMAC_CTX_free(HMAC_CTX *ctx) +{ + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); +} + +#endif + +void ptls_openssl_random_bytes(void *buf, size_t len) +{ + RAND_bytes(buf, (int)len); +} + +static EC_KEY *ecdh_gerenate_key(EC_GROUP *group) +{ + EC_KEY *key; + + if ((key = EC_KEY_new()) == NULL) + return NULL; + if (!EC_KEY_set_group(key, group) || !EC_KEY_generate_key(key)) { + EC_KEY_free(key); + return NULL; + } + + return key; +} + +static int ecdh_calc_secret(ptls_iovec_t *out, EC_GROUP *group, EC_KEY *privkey, EC_POINT *peer_point) +{ + ptls_iovec_t secret; + int ret; + + secret.len = (EC_GROUP_get_degree(group) + 7) / 8; + if ((secret.base = malloc(secret.len)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (ECDH_compute_key(secret.base, secret.len, peer_point, privkey, NULL) <= 0) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; /* ??? */ + goto Exit; + } + ret = 0; + +Exit: + if (ret == 0) { + *out = secret; + } else { + free(secret.base); + *out = (ptls_iovec_t){NULL}; + } + return ret; +} + +static EC_POINT *x9_62_decode_point(EC_GROUP *group, ptls_iovec_t vec, BN_CTX *bn_ctx) +{ + EC_POINT *point = NULL; + + if ((point = EC_POINT_new(group)) == NULL) + return NULL; + if (!EC_POINT_oct2point(group, point, vec.base, vec.len, bn_ctx)) { + EC_POINT_free(point); + return NULL; + } + + return point; +} + +static ptls_iovec_t x9_62_encode_point(EC_GROUP *group, const EC_POINT *point, BN_CTX *bn_ctx) +{ + ptls_iovec_t vec; + + if ((vec.len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx)) == 0) + return (ptls_iovec_t){NULL}; + if ((vec.base = malloc(vec.len)) == NULL) + return (ptls_iovec_t){NULL}; + if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, vec.base, vec.len, bn_ctx) != vec.len) { + free(vec.base); + return (ptls_iovec_t){NULL}; + } + + return vec; +} + +struct st_x9_62_keyex_context_t { + ptls_key_exchange_context_t super; + BN_CTX *bn_ctx; + EC_GROUP *group; + EC_KEY *privkey; + ptls_iovec_t pubkey; +}; + +static void x9_62_free_context(struct st_x9_62_keyex_context_t *ctx) +{ + free(ctx->pubkey.base); + if (ctx->privkey != NULL) + EC_KEY_free(ctx->privkey); + if (ctx->group != NULL) + EC_GROUP_free(ctx->group); + if (ctx->bn_ctx != NULL) + BN_CTX_free(ctx->bn_ctx); + free(ctx); +} + +static int x9_62_on_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + struct st_x9_62_keyex_context_t *ctx = (struct st_x9_62_keyex_context_t *)*_ctx; + EC_POINT *peer_point = NULL; + int ret; + + *_ctx = NULL; + + if ((peer_point = x9_62_decode_point(ctx->group, peerkey, ctx->bn_ctx)) == NULL) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + if ((ret = ecdh_calc_secret(secret, ctx->group, ctx->privkey, peer_point)) != 0) + goto Exit; + +Exit: + if (peer_point != NULL) + EC_POINT_free(peer_point); + x9_62_free_context(ctx); + return ret; +} + +static int x9_62_create_key_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *pubkey, int nid) +{ + struct st_x9_62_keyex_context_t *ctx = NULL; + int ret; + + if ((ctx = (struct st_x9_62_keyex_context_t *)malloc(sizeof(*ctx))) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + *ctx = (struct st_x9_62_keyex_context_t){{x9_62_on_exchange}}; + + if ((ctx->bn_ctx = BN_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if ((ctx->group = EC_GROUP_new_by_curve_name(nid)) == NULL) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if ((ctx->privkey = ecdh_gerenate_key(ctx->group)) == NULL) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + + if ((ctx->pubkey = x9_62_encode_point(ctx->group, EC_KEY_get0_public_key(ctx->privkey), ctx->bn_ctx)).base == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + *pubkey = ctx->pubkey; + ret = 0; + +Exit: + if (ret == 0) { + *_ctx = &ctx->super; + } else { + if (ctx != NULL) + x9_62_free_context(ctx); + *_ctx = NULL; + *pubkey = (ptls_iovec_t){NULL}; + } + + return ret; +} + +static int secp256r1_create_key_exchange(ptls_key_exchange_context_t **ctx, ptls_iovec_t *pubkey) +{ + return x9_62_create_key_exchange(ctx, pubkey, NID_X9_62_prime256v1); +} + +static int x9_62_key_exchange(EC_GROUP *group, ptls_iovec_t *pubkey, ptls_iovec_t *secret, ptls_iovec_t peerkey, BN_CTX *bn_ctx) +{ + EC_POINT *peer_point = NULL; + EC_KEY *privkey = NULL; + int ret; + + *pubkey = (ptls_iovec_t){NULL}; + *secret = (ptls_iovec_t){NULL}; + + /* decode peer key */ + if ((peer_point = x9_62_decode_point(group, peerkey, bn_ctx)) == NULL) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + + /* create private key */ + if ((privkey = ecdh_gerenate_key(group)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + /* encode public key */ + if ((*pubkey = x9_62_encode_point(group, EC_KEY_get0_public_key(privkey), bn_ctx)).base == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + /* calc secret */ + secret->len = (EC_GROUP_get_degree(group) + 7) / 8; + if ((secret->base = malloc(secret->len)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + /* ecdh! */ + if (ECDH_compute_key(secret->base, secret->len, peer_point, privkey, NULL) <= 0) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; /* ??? */ + goto Exit; + } + + ret = 0; + +Exit: + if (peer_point != NULL) + EC_POINT_free(peer_point); + if (privkey != NULL) + EC_KEY_free(privkey); + if (ret != 0) { + free(pubkey->base); + *pubkey = (ptls_iovec_t){NULL}; + free(secret->base); + *secret = (ptls_iovec_t){NULL}; + } + return ret; +} + +static int secp_key_exchange(int nid, ptls_iovec_t *pubkey, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + EC_GROUP *group = NULL; + BN_CTX *bn_ctx = NULL; + int ret; + + if ((group = EC_GROUP_new_by_curve_name(nid)) == NULL) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if ((bn_ctx = BN_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + ret = x9_62_key_exchange(group, pubkey, secret, peerkey, bn_ctx); + +Exit: + if (bn_ctx != NULL) + BN_CTX_free(bn_ctx); + if (group != NULL) + EC_GROUP_free(group); + return ret; +} + +static int secp256r1_key_exchange(ptls_iovec_t *pubkey, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + return secp_key_exchange(NID_X9_62_prime256v1, pubkey, secret, peerkey); +} + +static int do_sign(EVP_PKEY *key, ptls_buffer_t *outbuf, ptls_iovec_t input, const EVP_MD *md) +{ + EVP_MD_CTX *ctx = NULL; + EVP_PKEY_CTX *pkey_ctx; + size_t siglen; + int ret; + + if ((ctx = EVP_MD_CTX_create()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (EVP_DigestSignInit(ctx, &pkey_ctx, md, NULL, key) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_id(key) == EVP_PKEY_RSA) { + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha256()) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + } + if (EVP_DigestSignUpdate(ctx, input.base, input.len) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_DigestSignFinal(ctx, NULL, &siglen) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0) + goto Exit; + if (EVP_DigestSignFinal(ctx, outbuf->base + outbuf->off, &siglen) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + outbuf->off += siglen; + + ret = 0; +Exit: + if (ctx != NULL) + EVP_MD_CTX_destroy(ctx); + return ret; +} + +struct cipher_context_t { + ptls_cipher_context_t super; + EVP_CIPHER_CTX *evp; +}; + +static void cipher_dispose(ptls_cipher_context_t *_ctx) +{ + struct cipher_context_t *ctx = (struct cipher_context_t *)_ctx; + EVP_CIPHER_CTX_free(ctx->evp); +} + +static void cipher_do_init(ptls_cipher_context_t *_ctx, const void *iv) +{ + struct cipher_context_t *ctx = (struct cipher_context_t *)_ctx; + int ret; + ret = EVP_EncryptInit_ex(ctx->evp, NULL, NULL, NULL, iv); + assert(ret); +} + +static int cipher_setup_crypto(ptls_cipher_context_t *_ctx, const void *key, const EVP_CIPHER *cipher, + void (*do_transform)(ptls_cipher_context_t *, void *, const void *, size_t)) +{ + struct cipher_context_t *ctx = (struct cipher_context_t *)_ctx; + + ctx->super.do_dispose = cipher_dispose; + ctx->super.do_init = cipher_do_init; + ctx->super.do_transform = do_transform; + + if ((ctx->evp = EVP_CIPHER_CTX_new()) == NULL) + return PTLS_ERROR_NO_MEMORY; + if (!EVP_EncryptInit_ex(ctx->evp, cipher, NULL, key, NULL)) { + EVP_CIPHER_CTX_free(ctx->evp); + return PTLS_ERROR_LIBRARY; + } + + return 0; +} + +static void cipher_encrypt(ptls_cipher_context_t *_ctx, void *output, const void *input, size_t _len) +{ + struct cipher_context_t *ctx = (struct cipher_context_t *)_ctx; + int len = (int)_len, ret = EVP_EncryptUpdate(ctx->evp, output, &len, input, len); + assert(ret); +} + +static int aes128ctr_setup_crypto(ptls_cipher_context_t *ctx, int is_enc, const void *key) +{ + return cipher_setup_crypto(ctx, key, EVP_aes_128_ctr(), cipher_encrypt); +} + +static int aes256ctr_setup_crypto(ptls_cipher_context_t *ctx, int is_enc, const void *key) +{ + return cipher_setup_crypto(ctx, key, EVP_aes_256_ctr(), cipher_encrypt); +} + +#if defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) + +static int chacha20_setup_crypto(ptls_cipher_context_t *ctx, int is_enc, const void *key) +{ + return cipher_setup_crypto(ctx, key, EVP_chacha20(), cipher_encrypt); +} + +#endif + +struct aead_crypto_context_t { + ptls_aead_context_t super; + EVP_CIPHER_CTX *evp_ctx; +}; + +static void aead_dispose_crypto(ptls_aead_context_t *_ctx) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + + if (ctx->evp_ctx != NULL) + EVP_CIPHER_CTX_free(ctx->evp_ctx); +} + +static void aead_do_encrypt_init(ptls_aead_context_t *_ctx, const void *iv, const void *aad, size_t aadlen) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + int ret; + + /* FIXME for performance, preserve the expanded key instead of the raw key */ + ret = EVP_EncryptInit_ex(ctx->evp_ctx, NULL, NULL, NULL, iv); + assert(ret); + + if (aadlen != 0) { + int blocklen; + ret = EVP_EncryptUpdate(ctx->evp_ctx, NULL, &blocklen, aad, (int)aadlen); + assert(ret); + } +} + +static size_t aead_do_encrypt_update(ptls_aead_context_t *_ctx, void *output, const void *input, size_t inlen) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + int blocklen, ret; + + ret = EVP_EncryptUpdate(ctx->evp_ctx, output, &blocklen, input, (int)inlen); + assert(ret); + + return blocklen; +} + +static size_t aead_do_encrypt_final(ptls_aead_context_t *_ctx, void *_output) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + uint8_t *output = _output; + size_t off = 0, tag_size = ctx->super.algo->tag_size; + int blocklen, ret; + + ret = EVP_EncryptFinal_ex(ctx->evp_ctx, output + off, &blocklen); + assert(ret); + off += blocklen; + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_GCM_GET_TAG, (int)tag_size, output + off); + assert(ret); + off += tag_size; + + return off; +} + +static size_t aead_do_decrypt(ptls_aead_context_t *_ctx, void *_output, const void *input, size_t inlen, const void *iv, + const void *aad, size_t aadlen) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + uint8_t *output = _output; + size_t off = 0, tag_size = ctx->super.algo->tag_size; + int blocklen, ret; + + if (inlen < tag_size) + return SIZE_MAX; + + ret = EVP_DecryptInit_ex(ctx->evp_ctx, NULL, NULL, NULL, iv); + assert(ret); + if (aadlen != 0) { + ret = EVP_DecryptUpdate(ctx->evp_ctx, NULL, &blocklen, aad, (int)aadlen); + assert(ret); + } + ret = EVP_DecryptUpdate(ctx->evp_ctx, output + off, &blocklen, input, (int)(inlen - tag_size)); + assert(ret); + off += blocklen; + if (!EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_GCM_SET_TAG, (int)tag_size, (void *)((uint8_t *)input + inlen - tag_size))) + return SIZE_MAX; + if (!EVP_DecryptFinal_ex(ctx->evp_ctx, output + off, &blocklen)) + return SIZE_MAX; + off += blocklen; + + return off; +} + +static int aead_setup_crypto(ptls_aead_context_t *_ctx, int is_enc, const void *key, const EVP_CIPHER *cipher) +{ + struct aead_crypto_context_t *ctx = (struct aead_crypto_context_t *)_ctx; + int ret; + + ctx->super.dispose_crypto = aead_dispose_crypto; + if (is_enc) { + ctx->super.do_encrypt_init = aead_do_encrypt_init; + ctx->super.do_encrypt_update = aead_do_encrypt_update; + ctx->super.do_encrypt_final = aead_do_encrypt_final; + ctx->super.do_decrypt = NULL; + } else { + ctx->super.do_encrypt_init = NULL; + ctx->super.do_encrypt_update = NULL; + ctx->super.do_encrypt_final = NULL; + ctx->super.do_decrypt = aead_do_decrypt; + } + ctx->evp_ctx = NULL; + + if ((ctx->evp_ctx = EVP_CIPHER_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Error; + } + if (is_enc) { + if (!EVP_EncryptInit_ex(ctx->evp_ctx, cipher, NULL, key, NULL)) { + ret = PTLS_ERROR_LIBRARY; + goto Error; + } + } else { + if (!EVP_DecryptInit_ex(ctx->evp_ctx, cipher, NULL, key, NULL)) { + ret = PTLS_ERROR_LIBRARY; + goto Error; + } + } + if (!EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_GCM_SET_IVLEN, (int)ctx->super.algo->iv_size, NULL)) { + ret = PTLS_ERROR_LIBRARY; + goto Error; + } + + return 0; + +Error: + aead_dispose_crypto(&ctx->super); + return ret; +} + +static int aead_aes128gcm_setup_crypto(ptls_aead_context_t *ctx, int is_enc, const void *key) +{ + return aead_setup_crypto(ctx, is_enc, key, EVP_aes_128_gcm()); +} + +static int aead_aes256gcm_setup_crypto(ptls_aead_context_t *ctx, int is_enc, const void *key) +{ + return aead_setup_crypto(ctx, is_enc, key, EVP_aes_256_gcm()); +} + +#if defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) +static int aead_chacha20poly1305_setup_crypto(ptls_aead_context_t *ctx, int is_enc, const void *key) +{ + return aead_setup_crypto(ctx, is_enc, key, EVP_chacha20_poly1305()); +} +#endif + +#define _sha256_final(ctx, md) SHA256_Final((md), (ctx)) +ptls_define_hash(sha256, SHA256_CTX, SHA256_Init, SHA256_Update, _sha256_final); + +#define _sha384_final(ctx, md) SHA384_Final((md), (ctx)) +ptls_define_hash(sha384, SHA512_CTX, SHA384_Init, SHA384_Update, _sha384_final); + +static int sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *outbuf, + ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms) +{ + ptls_openssl_sign_certificate_t *self = (ptls_openssl_sign_certificate_t *)_self; + const struct st_ptls_openssl_signature_scheme_t *scheme; + + /* select the algorithm */ + for (scheme = self->schemes; scheme->scheme_id != UINT16_MAX; ++scheme) { + size_t i; + for (i = 0; i != num_algorithms; ++i) + if (algorithms[i] == scheme->scheme_id) + goto Found; + } + return PTLS_ALERT_HANDSHAKE_FAILURE; + +Found: + *selected_algorithm = scheme->scheme_id; + return do_sign(self->key, outbuf, input, scheme->scheme_md); +} + +static X509 *to_x509(ptls_iovec_t vec) +{ + const uint8_t *p = vec.base; + return d2i_X509(NULL, &p, vec.len); +} + +static int verify_sign(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t signature) +{ + EVP_PKEY *key = verify_ctx; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY_CTX *pkey_ctx = NULL; + int ret = 0; + + if (data.base == NULL) + goto Exit; + + if ((ctx = EVP_MD_CTX_create()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (EVP_DigestVerifyInit(ctx, &pkey_ctx, EVP_sha256(), NULL, key) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_id(key) == EVP_PKEY_RSA) { + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha256()) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + } + if (EVP_DigestVerifyUpdate(ctx, data.base, data.len) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_DigestVerifyFinal(ctx, signature.base, signature.len) != 1) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + ret = 0; + +Exit: + if (ctx != NULL) + EVP_MD_CTX_destroy(ctx); + EVP_PKEY_free(key); + return ret; +} + +int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key) +{ + *self = (ptls_openssl_sign_certificate_t){{sign_certificate}}; + size_t scheme_index = 0; + +#define PUSH_SCHEME(id, md) \ + self->schemes[scheme_index++] = (struct st_ptls_openssl_signature_scheme_t) \ + { \ + id, md \ + } + + switch (EVP_PKEY_id(key)) { + case EVP_PKEY_RSA: + PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, EVP_sha256()); + PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384, EVP_sha384()); + PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512, EVP_sha512()); + break; + case EVP_PKEY_EC: { + EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(key); + switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))) { + case NID_X9_62_prime256v1: + PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256, EVP_sha256()); + break; +#if defined(NID_secp384r1) && !OPENSSL_NO_SHA384 + case NID_secp384r1: + PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384, EVP_sha384()); + break; +#endif +#if defined(NID_secp384r1) && !OPENSSL_NO_SHA512 + case NID_secp521r1: + PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512, EVP_sha512()); + break; +#endif + default: + EC_KEY_free(eckey); + return PTLS_ERROR_INCOMPATIBLE_KEY; + } + EC_KEY_free(eckey); + } break; + default: + return PTLS_ERROR_INCOMPATIBLE_KEY; + } + PUSH_SCHEME(UINT16_MAX, NULL); + assert(scheme_index <= sizeof(self->schemes) / sizeof(self->schemes[0])); + +#undef PUSH_SCHEME + + EVP_PKEY_up_ref(key); + self->key = key; + + return 0; +} + +void ptls_openssl_dispose_sign_certificate(ptls_openssl_sign_certificate_t *self) +{ + EVP_PKEY_free(self->key); +} + +static int serialize_cert(X509 *cert, ptls_iovec_t *dst) +{ + int len = i2d_X509(cert, NULL); + assert(len > 0); + + if ((dst->base = malloc(len)) == NULL) + return PTLS_ERROR_NO_MEMORY; + unsigned char *p = dst->base; + dst->len = i2d_X509(cert, &p); + assert(len == dst->len); + + return 0; +} + +int ptls_openssl_load_certificates(ptls_context_t *ctx, X509 *cert, STACK_OF(X509) * chain) +{ + ptls_iovec_t *list = NULL; + size_t slot = 0, count = (cert != NULL) + (chain != NULL ? sk_X509_num(chain) : 0); + int ret; + + assert(ctx->certificates.list == NULL); + + if ((list = malloc(sizeof(*list) * count)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (cert != NULL) { + if ((ret = serialize_cert(cert, list + slot++)) != 0) + goto Exit; + } + if (chain != NULL) { + int i; + for (i = 0; i != sk_X509_num(chain); ++i) { + if ((ret = serialize_cert(sk_X509_value(chain, i), list + slot++)) != 0) + goto Exit; + } + } + + assert(slot == count); + + ctx->certificates.list = list; + ctx->certificates.count = count; + ret = 0; + +Exit: + if (ret != 0 && list != NULL) { + size_t i; + for (i = 0; i != slot; ++i) + free(list[i].base); + free(list); + } + return ret; +} + +static int verify_certificate(ptls_verify_certificate_t *_self, ptls_t *tls, int (**verifier)(void *, ptls_iovec_t, ptls_iovec_t), + void **verify_data, ptls_iovec_t *certs, size_t num_certs) +{ + ptls_openssl_verify_certificate_t *self = (ptls_openssl_verify_certificate_t *)_self; + X509 *cert = NULL; + STACK_OF(X509) *chain = NULL; + X509_STORE_CTX *verify_ctx = NULL; + int ret = 0; + + assert(num_certs != 0); + + if ((cert = to_x509(certs[0])) == NULL) { + ret = PTLS_ALERT_BAD_CERTIFICATE; + goto Exit; + } + + if (self->cert_store != NULL) { + size_t i; + for (i = 1; i != num_certs; ++i) { + X509 *interm = to_x509(certs[i]); + if (interm == NULL) { + ret = PTLS_ALERT_BAD_CERTIFICATE; + goto Exit; + } + } + if ((verify_ctx = X509_STORE_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (X509_STORE_CTX_init(verify_ctx, self->cert_store, cert, chain) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + X509_STORE_CTX_set_purpose(verify_ctx, X509_PURPOSE_SSL_CLIENT); + if (X509_verify_cert(verify_ctx) == 1) { + ret = 0; + } else { + switch (X509_STORE_CTX_get_error(verify_ctx)) { + case X509_V_ERR_OUT_OF_MEM: + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + case X509_V_ERR_CERT_REVOKED: + ret = PTLS_ALERT_CERTIFICATE_REVOKED; + goto Exit; + case X509_V_ERR_CERT_HAS_EXPIRED: + ret = PTLS_ALERT_CERTIFICATE_EXPIRED; + goto Exit; + default: + ret = PTLS_ALERT_CERTIFICATE_UNKNOWN; + goto Exit; + } + } + } + + if ((*verify_data = X509_get_pubkey(cert)) == NULL) { + ret = PTLS_ALERT_BAD_CERTIFICATE; + goto Exit; + } + *verifier = verify_sign; + +Exit: + if (verify_ctx != NULL) + X509_STORE_CTX_free(verify_ctx); + if (chain != NULL) + sk_X509_free(chain); + if (cert != NULL) + X509_free(cert); + return ret; +} + +int ptls_openssl_init_verify_certificate(ptls_openssl_verify_certificate_t *self, X509_STORE *store) +{ + *self = (ptls_openssl_verify_certificate_t){{verify_certificate}}; + + if (store != NULL) { + if (store != PTLS_OPENSSL_DEFAULT_CERTIFICATE_STORE) { + X509_STORE_up_ref(store); + self->cert_store = store; + } else { + X509_LOOKUP *lookup; + if ((self->cert_store = X509_STORE_new()) == NULL) + return -1; + if ((lookup = X509_STORE_add_lookup(self->cert_store, X509_LOOKUP_file())) == NULL) + return -1; + X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT); + if ((lookup = X509_STORE_add_lookup(self->cert_store, X509_LOOKUP_hash_dir())) == NULL) + return -1; + X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT); + } + } + + return 0; +} + +void ptls_openssl_dispose_verify_certificate(ptls_openssl_verify_certificate_t *self) +{ + X509_STORE_free(self->cert_store); + free(self); +} + +#define TICKET_LABEL_SIZE 16 +#define TICKET_IV_SIZE EVP_MAX_IV_LENGTH + +int ptls_openssl_encrypt_ticket(ptls_buffer_t *buf, ptls_iovec_t src, + int (*cb)(unsigned char *key_name, unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc)) +{ + EVP_CIPHER_CTX *cctx = NULL; + HMAC_CTX *hctx = NULL; + uint8_t *dst; + int clen, ret; + + if ((cctx = EVP_CIPHER_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if ((hctx = HMAC_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + if ((ret = ptls_buffer_reserve(buf, TICKET_LABEL_SIZE + TICKET_IV_SIZE + src.len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE)) != + 0) + goto Exit; + dst = buf->base + buf->off; + + /* fill label and iv, as well as obtaining the keys */ + if (!(*cb)(dst, dst + TICKET_LABEL_SIZE, cctx, hctx, 1)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + dst += TICKET_LABEL_SIZE + TICKET_IV_SIZE; + + /* encrypt */ + if (!EVP_EncryptUpdate(cctx, dst, &clen, src.base, (int)src.len)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + dst += clen; + if (!EVP_EncryptFinal_ex(cctx, dst, &clen)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + dst += clen; + + /* append hmac */ + if (!HMAC_Update(hctx, buf->base + buf->off, dst - (buf->base + buf->off)) || !HMAC_Final(hctx, dst, NULL)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + dst += HMAC_size(hctx); + + assert(dst <= buf->base + buf->capacity); + buf->off += dst - (buf->base + buf->off); + ret = 0; + +Exit: + if (cctx != NULL) + EVP_CIPHER_CTX_cleanup(cctx); + if (hctx != NULL) + HMAC_CTX_free(hctx); + return ret; +} + +int ptls_openssl_decrypt_ticket(ptls_buffer_t *buf, ptls_iovec_t src, + int (*cb)(unsigned char *key_name, unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc)) +{ + EVP_CIPHER_CTX *cctx = NULL; + HMAC_CTX *hctx = NULL; + int clen, ret; + + if ((cctx = EVP_CIPHER_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if ((hctx = HMAC_CTX_new()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + /* obtain cipher and hash context. + * Note: no need to handle renew, since in picotls we always send a new ticket to minimize the chance of ticket reuse */ + if (src.len < TICKET_LABEL_SIZE + TICKET_IV_SIZE) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + if (!(*cb)(src.base, src.base + TICKET_LABEL_SIZE, cctx, hctx, 0)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + + /* check hmac, and exclude label, iv, hmac */ + size_t hmac_size = HMAC_size(hctx); + if (src.len < TICKET_LABEL_SIZE + TICKET_IV_SIZE + hmac_size) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + src.len -= hmac_size; + uint8_t hmac[EVP_MAX_MD_SIZE]; + if (!HMAC_Update(hctx, src.base, src.len) || !HMAC_Final(hctx, hmac, NULL)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (memcmp(src.base + src.len, hmac, hmac_size) != 0) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; + goto Exit; + } + src.base += TICKET_LABEL_SIZE + TICKET_IV_SIZE; + src.len -= TICKET_LABEL_SIZE + TICKET_IV_SIZE; + + /* decrypt */ + if ((ret = ptls_buffer_reserve(buf, src.len)) != 0) + goto Exit; + if (!EVP_DecryptUpdate(cctx, buf->base + buf->off, &clen, src.base, (int)src.len)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + buf->off += clen; + if (!EVP_DecryptFinal_ex(cctx, buf->base + buf->off, &clen)) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + buf->off += clen; + + ret = 0; + +Exit: + if (cctx != NULL) + EVP_CIPHER_CTX_cleanup(cctx); + if (hctx != NULL) + HMAC_CTX_free(hctx); + return ret; +} + +ptls_key_exchange_algorithm_t ptls_openssl_secp256r1 = {PTLS_GROUP_SECP256R1, secp256r1_create_key_exchange, + secp256r1_key_exchange}; +ptls_key_exchange_algorithm_t *ptls_openssl_key_exchanges[] = {&ptls_openssl_secp256r1, NULL}; +ptls_cipher_algorithm_t ptls_openssl_aes128ctr = {"AES128-CTR", PTLS_AES128_KEY_SIZE, PTLS_AES_IV_SIZE, + sizeof(struct cipher_context_t), aes128ctr_setup_crypto}; +ptls_aead_algorithm_t ptls_openssl_aes128gcm = {"AES128-GCM", + &ptls_openssl_aes128ctr, + PTLS_AES128_KEY_SIZE, + PTLS_AESGCM_IV_SIZE, + PTLS_AESGCM_TAG_SIZE, + sizeof(struct aead_crypto_context_t), + aead_aes128gcm_setup_crypto}; +ptls_cipher_algorithm_t ptls_openssl_aes256ctr = {"AES256-CTR", PTLS_AES256_KEY_SIZE, PTLS_AES_IV_SIZE, + sizeof(struct cipher_context_t), aes256ctr_setup_crypto}; +ptls_aead_algorithm_t ptls_openssl_aes256gcm = {"AES256-GCM", + &ptls_openssl_aes256ctr, + PTLS_AES256_KEY_SIZE, + PTLS_AESGCM_IV_SIZE, + PTLS_AESGCM_TAG_SIZE, + sizeof(struct aead_crypto_context_t), + aead_aes256gcm_setup_crypto}; +ptls_hash_algorithm_t ptls_openssl_sha256 = {PTLS_SHA256_BLOCK_SIZE, PTLS_SHA256_DIGEST_SIZE, sha256_create, + PTLS_ZERO_DIGEST_SHA256}; +ptls_hash_algorithm_t ptls_openssl_sha384 = {PTLS_SHA384_BLOCK_SIZE, PTLS_SHA384_DIGEST_SIZE, sha384_create, + PTLS_ZERO_DIGEST_SHA384}; +ptls_cipher_suite_t ptls_openssl_aes128gcmsha256 = {PTLS_CIPHER_SUITE_AES_128_GCM_SHA256, &ptls_openssl_aes128gcm, + &ptls_openssl_sha256}; +ptls_cipher_suite_t ptls_openssl_aes256gcmsha384 = {PTLS_CIPHER_SUITE_AES_256_GCM_SHA384, &ptls_openssl_aes256gcm, + &ptls_openssl_sha384}; +#if defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) +ptls_cipher_algorithm_t ptls_openssl_chacha20 = {"CHACHA20", PTLS_CHACHA20_KEY_SIZE, PTLS_CHACHA20_IV_SIZE, + sizeof(struct cipher_context_t), chacha20_setup_crypto}; +ptls_aead_algorithm_t ptls_openssl_chacha20poly1305 = {"CHACHA20-POLY1305", + &ptls_openssl_chacha20, + PTLS_CHACHA20_KEY_SIZE, + PTLS_CHACHA20POLY1305_IV_SIZE, + PTLS_CHACHA20POLY1305_TAG_SIZE, + sizeof(struct aead_crypto_context_t), + aead_chacha20poly1305_setup_crypto}; +ptls_cipher_suite_t ptls_openssl_chacha20poly1305sha256 = {PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256, + &ptls_openssl_chacha20poly1305, &ptls_openssl_sha256}; +#endif +ptls_cipher_suite_t *ptls_openssl_cipher_suites[] = {&ptls_openssl_aes256gcmsha384, &ptls_openssl_aes128gcmsha256, +#if defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) + &ptls_openssl_chacha20poly1305sha256, +#endif + NULL}; diff --git a/web/server/h2o/libh2o/deps/picotls/lib/pembase64.c b/web/server/h2o/libh2o/deps/picotls/lib/pembase64.c new file mode 100644 index 00000000..de44c538 --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/pembase64.c @@ -0,0 +1,373 @@ +/* +* Copyright (c) 2016 Christian Huitema <huitema@huitema.net> +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* + * Manage Base64 encoding. + */ +#ifdef _WINDOWS +#include "wincompat.h" +#else +#include <sys/time.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "picotls.h" +#include "picotls/pembase64.h" + +static char ptls_base64_alphabet[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + +static char ptls_base64_values[] = { + /* 0x00 to 0x0F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x10 to 0x1F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x20 to 0x2F. '+' at 2B, '/' at 2F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + /* 0x30 to 0x3F -- digits 0 to 9 at 0x30 to 0x39*/ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + /* 0x40 to 0x4F -- chars 'A' to 'O' at 0x41 to 0x4F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 0x50 to 0x5F -- chars 'P' to 'Z' at 0x50 to 0x5A */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + /* 0x60 to 0x6F -- chars 'a' to 'o' at 0x61 to 0x6F */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + /* 0x70 to 0x7F -- chars 'p' to 'z' at 0x70 to 0x7A */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1}; + +static void ptls_base64_cell(const uint8_t *data, char *text) +{ + int n[4]; + + n[0] = data[0] >> 2; + n[1] = ((data[0] & 3) << 4) | (data[1] >> 4); + n[2] = ((data[1] & 15) << 2) | (data[2] >> 6); + n[3] = data[2] & 63; + + for (int i = 0; i < 4; i++) { + text[i] = ptls_base64_alphabet[n[i]]; + } +} + +size_t ptls_base64_howlong(size_t data_length) +{ + return (((data_length + 2) / 3) * 4); +} + +int ptls_base64_encode(const uint8_t *data, size_t data_len, char *ptls_base64_text) +{ + int l = 0; + int lt = 0; + + while ((data_len - l) >= 3) { + ptls_base64_cell(data + l, ptls_base64_text + lt); + l += 3; + lt += 4; + } + + switch (data_len - l) { + case 0: + break; + case 1: + ptls_base64_text[lt++] = ptls_base64_alphabet[data[l] >> 2]; + ptls_base64_text[lt++] = ptls_base64_alphabet[(data[l] & 3) << 4]; + ptls_base64_text[lt++] = '='; + ptls_base64_text[lt++] = '='; + break; + case 2: + ptls_base64_text[lt++] = ptls_base64_alphabet[data[l] >> 2]; + ptls_base64_text[lt++] = ptls_base64_alphabet[((data[l] & 3) << 4) | (data[l + 1] >> 4)]; + ptls_base64_text[lt++] = ptls_base64_alphabet[((data[l + 1] & 15) << 2)]; + ptls_base64_text[lt++] = '='; + break; + default: + break; + } + ptls_base64_text[lt++] = 0; + + return lt; +} + +/* + * Take into input a line of text, so as to work by increments. + * The intermediate text of the decoding is kept in a state variable. + * The decoded data is accumulated in a PTLS buffer. + * The parsing is consistent with the lax definition in RFC 7468 + */ + +void ptls_base64_decode_init(ptls_base64_decode_state_t *state) +{ + state->nbc = 0; + state->nbo = 3; + state->v = 0; + state->status = PTLS_BASE64_DECODE_IN_PROGRESS; +} + +int ptls_base64_decode(const char *text, ptls_base64_decode_state_t *state, ptls_buffer_t *buf) +{ + int ret = 0; + uint8_t decoded[3]; + size_t text_index = 0; + int c; + char vc; + + /* skip initial blanks */ + while (text[text_index] != 0) { + c = text[text_index]; + + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + text_index++; + } else { + break; + } + } + + while (text[text_index] != 0 && ret == 0 && state->status == PTLS_BASE64_DECODE_IN_PROGRESS) { + c = text[text_index++]; + + vc = 0 < c && c < 0x7f ? ptls_base64_values[c] : -1; + if (vc == -1) { + if (state->nbc == 2 && c == '=' && text[text_index] == '=') { + state->nbc = 4; + text_index++; + state->nbo = 1; + state->v <<= 12; + } else if (state->nbc == 3 && c == '=') { + state->nbc = 4; + state->nbo = 2; + state->v <<= 6; + } else { + /* Skip final blanks */ + text_index--; + while (text[text_index] != 0) { + c = text[text_index++]; + + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0x0B || c == 0x0C) { + continue; + } + } + + /* Should now be at end of buffer */ + if (text[text_index] == 0) { + break; + } else { + /* Not at end of buffer, signal a decoding error */ + state->nbo = 0; + state->status = PTLS_BASE64_DECODE_FAILED; + ret = PTLS_ERROR_INCORRECT_BASE64; + } + } + } else { + state->nbc++; + state->v <<= 6; + state->v |= vc; + } + + if (ret == 0 && state->nbc == 4) { + /* Convert to up to 3 octets */ + for (int j = 0; j < state->nbo; j++) { + decoded[j] = (uint8_t)(state->v >> (8 * (2 - j))); + } + + ret = ptls_buffer__do_pushv(buf, decoded, state->nbo); + + if (ret == 0) { + /* test for fin or continuation */ + if (state->nbo < 3) { + /* Check that there are only trainling blanks on this line */ + while (text[text_index] != 0) { + c = text[text_index++]; + + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0x0B || c == 0x0C) { + continue; + } + } + if (text[text_index] == 0) { + state->status = PTLS_BASE64_DECODE_DONE; + } else { + state->status = PTLS_BASE64_DECODE_FAILED; + ret = PTLS_ERROR_INCORRECT_BASE64; + } + break; + } else { + state->v = 0; + state->nbo = 3; + state->nbc = 0; + } + } + } + } + return ret; +} + +/* + * Reading a PEM file, to get an object: + * + * - Find first object, get the object name. + * - If object label is what the application expects, parse, else skip to end. + * + * The following labels are defined in RFC 7468: + * + * Sec. Label ASN.1 Type Reference Module + * ----+----------------------+-----------------------+---------+---------- + * 5 CERTIFICATE Certificate [RFC5280] id-pkix1-e + * 6 X509 CRL CertificateList [RFC5280] id-pkix1-e + * 7 CERTIFICATE REQUEST CertificationRequest [RFC2986] id-pkcs10 + * 8 PKCS7 ContentInfo [RFC2315] id-pkcs7* + * 9 CMS ContentInfo [RFC5652] id-cms2004 + * 10 PRIVATE KEY PrivateKeyInfo ::= [RFC5208] id-pkcs8 + * OneAsymmetricKey [RFC5958] id-aKPV1 + * 11 ENCRYPTED PRIVATE KEY EncryptedPrivateKeyInfo [RFC5958] id-aKPV1 + * 12 ATTRIBUTE CERTIFICATE AttributeCertificate [RFC5755] id-acv2 + * 13 PUBLIC KEY SubjectPublicKeyInfo [RFC5280] id-pkix1-e + */ + +static int ptls_compare_separator_line(const char *line, const char *begin_or_end, const char *label) +{ + int ret = strncmp(line, "-----", 5); + size_t text_index = 5; + + if (ret == 0) { + size_t begin_or_end_length = strlen(begin_or_end); + ret = strncmp(line + text_index, begin_or_end, begin_or_end_length); + text_index += begin_or_end_length; + } + + if (ret == 0) { + ret = line[text_index] - ' '; + text_index++; + } + + if (ret == 0) { + size_t label_length = strlen(label); + ret = strncmp(line + text_index, label, label_length); + text_index += label_length; + } + + if (ret == 0) { + ret = strncmp(line + text_index, "-----", 5); + } + + return ret; +} + +static int ptls_get_pem_object(FILE *F, const char *label, ptls_buffer_t *buf) +{ + int ret = PTLS_ERROR_PEM_LABEL_NOT_FOUND; + char line[256]; + ptls_base64_decode_state_t state; + + /* Get the label on a line by itself */ + while (fgets(line, 256, F)) { + if (ptls_compare_separator_line(line, "BEGIN", label) == 0) { + ret = 0; + ptls_base64_decode_init(&state); + break; + } + } + /* Get the data in the buffer */ + while (ret == 0 && fgets(line, 256, F)) { + if (ptls_compare_separator_line(line, "END", label) == 0) { + if (state.status == PTLS_BASE64_DECODE_DONE || (state.status == PTLS_BASE64_DECODE_IN_PROGRESS && state.nbc == 0)) { + ret = 0; + } else { + ret = PTLS_ERROR_INCORRECT_BASE64; + } + break; + } else { + ret = ptls_base64_decode(line, &state, buf); + } + } + + return ret; +} + +int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects) +{ + FILE *F; + int ret = 0; + size_t count = 0; +#ifdef _WINDOWS + errno_t err = fopen_s(&F, pem_fname, "r"); + if (err != 0) { + ret = -1; + } +#else + F = fopen(pem_fname, "r"); + if (F == NULL) { + ret = -1; + } +#endif + + *nb_objects = 0; + + if (ret == 0) { + while (count < list_max) { + ptls_buffer_t buf; + + ptls_buffer_init(&buf, "", 0); + + ret = ptls_get_pem_object(F, label, &buf); + + if (ret == 0) { + if (buf.off > 0 && buf.is_allocated) { + list[count].base = buf.base; + list[count].len = buf.off; + count++; + } else { + ptls_buffer_dispose(&buf); + } + } else { + ptls_buffer_dispose(&buf); + break; + } + } + } + + if (ret == PTLS_ERROR_PEM_LABEL_NOT_FOUND && count > 0) { + ret = 0; + } + + *nb_objects = count; + + if (F != NULL) { + fclose(F); + } + + return ret; +} + +#define PTLS_MAX_CERTS_IN_CONTEXT 16 + +int ptls_load_certificates(ptls_context_t *ctx, char *cert_pem_file) +{ + int ret = 0; + + ctx->certificates.list = (ptls_iovec_t *)malloc(PTLS_MAX_CERTS_IN_CONTEXT * sizeof(ptls_iovec_t)); + + if (ctx->certificates.list == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } else { + ret = ptls_load_pem_objects(cert_pem_file, "CERTIFICATE", ctx->certificates.list, PTLS_MAX_CERTS_IN_CONTEXT, + &ctx->certificates.count); + } + + return ret; +} 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}; + diff --git a/web/server/h2o/libh2o/deps/picotls/lib/uecc.c b/web/server/h2o/libh2o/deps/picotls/lib/uecc.c new file mode 100644 index 00000000..10f32520 --- /dev/null +++ b/web/server/h2o/libh2o/deps/picotls/lib/uecc.c @@ -0,0 +1,190 @@ +/* + * 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 <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef _WINDOWS +#include "wincompat.h" +#else +#include <unistd.h> +#endif +#include "sha2.h" +#include "uECC.h" +#include "uECC_vli.h" +#include "picotls.h" +#include "picotls/minicrypto.h" + +#define TYPE_UNCOMPRESSED_PUBLIC_KEY 4 + +struct st_secp256r1_key_exhchange_t { + ptls_key_exchange_context_t super; + uint8_t priv[SECP256R1_PRIVATE_KEY_SIZE]; + uint8_t pub[SECP256R1_PUBLIC_KEY_SIZE]; +}; + +static int secp256r1_on_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + struct st_secp256r1_key_exhchange_t *ctx = (struct st_secp256r1_key_exhchange_t *)*_ctx; + uint8_t *secbytes = NULL; + int ret; + + *_ctx = NULL; + + if (secret == NULL) { + ret = 0; + goto Exit; + } + + if (peerkey.len != SECP256R1_PUBLIC_KEY_SIZE || peerkey.base[0] != TYPE_UNCOMPRESSED_PUBLIC_KEY) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + if ((secbytes = (uint8_t *)malloc(SECP256R1_SHARED_SECRET_SIZE)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if (!uECC_shared_secret(peerkey.base + 1, ctx->priv, secbytes, uECC_secp256r1())) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + *secret = ptls_iovec_init(secbytes, SECP256R1_SHARED_SECRET_SIZE); + ret = 0; + +Exit: + if (ret != 0) + free(secbytes); + ptls_clear_memory(ctx->priv, sizeof(ctx->priv)); + free(ctx); + return ret; +} + +static int secp256r1_create_key_exchange(ptls_key_exchange_context_t **_ctx, ptls_iovec_t *pubkey) +{ + struct st_secp256r1_key_exhchange_t *ctx; + + if ((ctx = (struct st_secp256r1_key_exhchange_t *)malloc(sizeof(*ctx))) == NULL) + return PTLS_ERROR_NO_MEMORY; + ctx->super = (ptls_key_exchange_context_t){secp256r1_on_exchange}; + ctx->pub[0] = TYPE_UNCOMPRESSED_PUBLIC_KEY; + uECC_make_key(ctx->pub + 1, ctx->priv, uECC_secp256r1()); + + *_ctx = &ctx->super; + *pubkey = ptls_iovec_init(ctx->pub, sizeof(ctx->pub)); + return 0; +} + +static int secp256r1_key_exchange(ptls_iovec_t *pubkey, ptls_iovec_t *secret, ptls_iovec_t peerkey) +{ + uint8_t priv[SECP256R1_PRIVATE_KEY_SIZE], *pub = NULL, *secbytes = NULL; + int ret; + + if (peerkey.len != SECP256R1_PUBLIC_KEY_SIZE || peerkey.base[0] != TYPE_UNCOMPRESSED_PUBLIC_KEY) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + if ((pub = malloc(SECP256R1_PUBLIC_KEY_SIZE)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + if ((secbytes = malloc(SECP256R1_SHARED_SECRET_SIZE)) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + goto Exit; + } + + pub[0] = TYPE_UNCOMPRESSED_PUBLIC_KEY; + uECC_make_key(pub + 1, priv, uECC_secp256r1()); + if (!uECC_shared_secret(peerkey.base + 1, priv, secbytes, uECC_secp256r1())) { + ret = PTLS_ALERT_DECRYPT_ERROR; + goto Exit; + } + *pubkey = ptls_iovec_init(pub, SECP256R1_PUBLIC_KEY_SIZE); + *secret = ptls_iovec_init(secbytes, SECP256R1_SHARED_SECRET_SIZE); + ret = 0; + +Exit: + ptls_clear_memory(priv, sizeof(priv)); + if (ret != 0) { + free(secbytes); + free(pub); + } + return ret; +} + +static int secp256r1sha256_sign(ptls_sign_certificate_t *_self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *outbuf, + ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms) +{ + ptls_minicrypto_secp256r1sha256_sign_certificate_t *self = (ptls_minicrypto_secp256r1sha256_sign_certificate_t *)_self; + uint8_t hash[32], sig[64]; + size_t i; + int ret; + + /* check algorithm */ + for (i = 0; i != num_algorithms; ++i) + if (algorithms[i] == PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256) + break; + if (i == num_algorithms) + return PTLS_ALERT_HANDSHAKE_FAILURE; + + { /* calc hash */ + cf_sha256_context ctx; + cf_sha256_init(&ctx); + cf_sha256_update(&ctx, input.base, input.len); + cf_sha256_digest_final(&ctx, hash); + ptls_clear_memory(&ctx, sizeof(ctx)); + } + + /* sign */ + uECC_sign(self->key, hash, sizeof(hash), sig, uECC_secp256r1()); + + /* encode using DER */ + ptls_buffer_push_asn1_sequence(outbuf, { + if ((ret = ptls_buffer_push_asn1_ubigint(outbuf, sig, 32)) != 0) + goto Exit; + if ((ret = ptls_buffer_push_asn1_ubigint(outbuf, sig + 32, 32)) != 0) + goto Exit; + }); + + *selected_algorithm = PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256; + ret = 0; + +Exit: + ptls_clear_memory(hash, sizeof(hash)); + ptls_clear_memory(sig, sizeof(sig)); + return ret; +} + +int ptls_minicrypto_init_secp256r1sha256_sign_certificate(ptls_minicrypto_secp256r1sha256_sign_certificate_t *self, + ptls_iovec_t key) +{ + if (key.len != sizeof(self->key)) + return PTLS_ERROR_INCOMPATIBLE_KEY; + + self->super.cb = secp256r1sha256_sign; + memcpy(self->key, key.base, sizeof(self->key)); + + return 0; +} + +ptls_key_exchange_algorithm_t ptls_minicrypto_secp256r1 = {PTLS_GROUP_SECP256R1, secp256r1_create_key_exchange, + secp256r1_key_exchange}; +ptls_key_exchange_algorithm_t *ptls_minicrypto_key_exchanges[] = {&ptls_minicrypto_secp256r1, NULL}; |