3313 lines
112 KiB
C
3313 lines
112 KiB
C
/*
|
|
* Author: Christian Huitema
|
|
* Copyright (c) 2017, Private Octopus, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
/* The tls_api.c file provides the glue between the QUIC protocol code and the
|
|
* implementation of TLS 1.3 in picotls. That glue code has two main components:
|
|
*
|
|
* - matching TLS concepts such as handshake events with QUIC protocol events.
|
|
* - providing implementation of cryptographic algorithms.
|
|
*
|
|
* The bulk of the code corresponds to the first objective, the implementation
|
|
* of generic interactions with picotls. But this implementation relies on
|
|
* implementation of cryptographic primitives. The initial version of
|
|
* picoquic relies on OpenSSL for the implementation of these primitives,
|
|
* which limits portability of Picoquic to platforms that support OpenSSL.
|
|
*
|
|
* Picotls includes API for providing a variety of implementations of the
|
|
* cryptographic algorithms, linking with external libraries like OpenSSL or
|
|
* minimal implementations like "minicrypto". Our goal there is to provide a
|
|
* "crypto-provider" API so that applications can decide which provider they
|
|
* prefer.
|
|
*
|
|
* As an intermediate state towards that goal, we isolate the dependencies on
|
|
* OpenSSL in a small set of function calls.
|
|
*/
|
|
|
|
#ifdef _WINDOWS
|
|
#include "wincompat.h"
|
|
#endif
|
|
#include <stddef.h>
|
|
#include "picotls.h"
|
|
#include "picoquic_internal.h"
|
|
#include "picotls/openssl.h"
|
|
#if !defined(_WINDOWS) || defined(_WINDOWS64)
|
|
#include "picotls/fusion.h"
|
|
#endif
|
|
#if 0
|
|
#include "picotls/ffx.h"
|
|
#endif
|
|
#include "tls_api.h"
|
|
#include <openssl/pem.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/engine.h>
|
|
#include <openssl/conf.h>
|
|
#include <openssl/ssl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "picoquic_unified_log.h"
|
|
|
|
#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))
|
|
|
|
#ifndef UNREFERENCED_PARAMETER
|
|
#define UNREFERENCED_PARAMETER(x) (void)(x)
|
|
#endif
|
|
|
|
|
|
#define PICOQUIC_TRANSPORT_PARAMETERS_TLS_EXTENSION 0xFFA5
|
|
#define PICOQUIC_TRANSPORT_PARAMETERS_MAX_SIZE 2048
|
|
|
|
#ifdef PTLS_ESNI_NONCE_SIZE
|
|
#define PICOQUIC_ESNI_NONCE_SIZE PTLS_ESNI_NONCE_SIZE
|
|
#else
|
|
#define PICOQUIC_ESNI_NONCE_SIZE 16
|
|
#endif
|
|
|
|
|
|
typedef struct st_picoquic_tls_ctx_t {
|
|
ptls_t* tls;
|
|
picoquic_cnx_t* cnx;
|
|
int client_mode;
|
|
ptls_raw_extension_t ext[2];
|
|
ptls_handshake_properties_t handshake_properties;
|
|
ptls_iovec_t alpn_vec[PICOQUIC_ALPN_NUMBER_MAX];
|
|
int alpn_count;
|
|
uint8_t ext_data[PICOQUIC_TRANSPORT_PARAMETERS_MAX_SIZE];
|
|
uint8_t ext_received[PICOQUIC_TRANSPORT_PARAMETERS_MAX_SIZE];
|
|
size_t ext_received_length;
|
|
int ext_received_return;
|
|
uint16_t esni_version;
|
|
uint8_t esni_nonce[PICOQUIC_ESNI_NONCE_SIZE];
|
|
uint8_t app_secret_enc[PTLS_MAX_DIGEST_SIZE];
|
|
uint8_t app_secret_dec[PTLS_MAX_DIGEST_SIZE];
|
|
} picoquic_tls_ctx_t;
|
|
|
|
struct st_picoquic_log_event_t {
|
|
ptls_log_event_t super;
|
|
FILE* fp;
|
|
};
|
|
|
|
|
|
/* This first part of this file provides a set of function for accessing
|
|
* the cryptographic libraries.
|
|
* The explicit calls to
|
|
*/
|
|
#define CRYPTO_PROVIDERS_REGION 1
|
|
|
|
#ifdef CRYPTO_PROVIDERS_REGION
|
|
|
|
/*
|
|
* Make sure that openssl is properly initialized.
|
|
*
|
|
* The OpenSSL resources are allocated on first use, and not released until the end of the
|
|
* process. The only problem is when use memory leak tracers such as valgrind. The OpenSSL
|
|
* allocations will create a large number of issues, which may hide the actual leaks that
|
|
* should be fixed. To alleviate that, the application may use an explicit call to
|
|
* a global destructor like OPENSSL_cleanup(), but normally the OpenSSL stack does it
|
|
* during the process exit.
|
|
*/
|
|
static int openssl_is_init = 0;
|
|
|
|
static void picoquic_init_openssl()
|
|
{
|
|
if (openssl_is_init == 0) {
|
|
openssl_is_init = 1;
|
|
ERR_load_crypto_strings();
|
|
OpenSSL_add_all_algorithms();
|
|
#if !defined(OPENSSL_NO_ENGINE)
|
|
/* Load all compiled-in ENGINEs */
|
|
ENGINE_load_builtin_engines();
|
|
ENGINE_register_all_ciphers();
|
|
ENGINE_register_all_digests();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ptls_cipher_suite_t* picoquic_cipher_suites[] = {
|
|
&ptls_openssl_aes128gcmsha256,
|
|
&ptls_openssl_aes256gcmsha384,
|
|
#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
& ptls_openssl_chacha20poly1305sha256,
|
|
#else
|
|
/* No support for ChaCha 20 */
|
|
#endif
|
|
NULL };
|
|
|
|
#if !defined(_WINDOWS) || defined(_WINDOWS64)
|
|
/* Definition of fusion versions of AESGCM */
|
|
ptls_cipher_suite_t picoquic_fusion_aes128gcmsha256 = { PTLS_CIPHER_SUITE_AES_128_GCM_SHA256, &ptls_fusion_aes128gcm,
|
|
&ptls_openssl_sha256 };
|
|
ptls_cipher_suite_t picoquic_fusion_aes256gcmsha384 = { PTLS_CIPHER_SUITE_AES_256_GCM_SHA384, &ptls_fusion_aes256gcm,
|
|
&ptls_openssl_sha384 };
|
|
#endif
|
|
|
|
/* Setting of cipher suites. This is provisional code,
|
|
using the most performant functions from openssl or fusion */
|
|
|
|
static int picoquic_set_cipher_suite_list(ptls_cipher_suite_t** selected_suites, int cipher_suite_id)
|
|
{
|
|
int nb_suites = 0;
|
|
/* Check first if fusion is enabled */
|
|
#if !defined(_WINDOWS) || defined(_WINDOWS64)
|
|
if (ptls_fusion_is_supported_by_cpu()) {
|
|
if (cipher_suite_id == 0 || cipher_suite_id == 128) {
|
|
selected_suites[nb_suites++] = &picoquic_fusion_aes128gcmsha256;
|
|
}
|
|
|
|
if (cipher_suite_id == 0 || cipher_suite_id == 256) {
|
|
selected_suites[nb_suites++] = &picoquic_fusion_aes256gcmsha384;
|
|
}
|
|
}
|
|
#endif
|
|
if (nb_suites == 0) {
|
|
/* Fallback to openssl if fusion is not supported */
|
|
if (cipher_suite_id == 0 || cipher_suite_id == 128) {
|
|
selected_suites[nb_suites++] = &ptls_openssl_aes128gcmsha256;
|
|
}
|
|
|
|
if (cipher_suite_id == 0 || cipher_suite_id == 256) {
|
|
selected_suites[nb_suites++] = &ptls_openssl_aes256gcmsha384;
|
|
}
|
|
}
|
|
#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
if (cipher_suite_id == 0 || cipher_suite_id == 20) {
|
|
selected_suites[nb_suites++] = &ptls_openssl_chacha20poly1305sha256;
|
|
}
|
|
#else
|
|
/* Consider getting ChaCha20 from mini crypto, despite poor performance */
|
|
#endif
|
|
return nb_suites;
|
|
}
|
|
|
|
static int picoquic_set_cipher_suite_in_ctx(ptls_context_t* ctx, int cipher_suite_id)
|
|
{
|
|
ptls_cipher_suite_t** selected_suites = (ptls_cipher_suite_t**)malloc(sizeof(ptls_cipher_suite_t*) * 4);
|
|
int nb_suites = 0;
|
|
int ret = 0;
|
|
|
|
if (ctx == NULL || selected_suites == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
nb_suites = picoquic_set_cipher_suite_list(selected_suites, cipher_suite_id);
|
|
|
|
if (nb_suites == 0) {
|
|
ctx->cipher_suites = NULL;
|
|
ret = -1;
|
|
}
|
|
else {
|
|
while (nb_suites < 4) {
|
|
selected_suites[nb_suites++] = NULL;
|
|
}
|
|
ctx->cipher_suites = selected_suites;
|
|
}
|
|
}
|
|
|
|
if (ret != 0 && selected_suites != NULL) {
|
|
free((void*)selected_suites);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Obtain an AES128 ECB cipher using openSSL */
|
|
void* picoquic_aes128_ecb_openssl_create(int is_enc, const void* ecb_key)
|
|
{
|
|
return (void*)ptls_cipher_new(&ptls_openssl_aes128ecb, is_enc, ecb_key);
|
|
}
|
|
|
|
/* Obtain AES128GCM SHA256, AES256GCM_SHA384 or CHACHA20 suite according to current provider */
|
|
ptls_cipher_suite_t* picoquic_get_selected_cipher_suite_by_id(int cipher_suite_id)
|
|
{
|
|
ptls_cipher_suite_t* selected_suites[4];
|
|
ptls_cipher_suite_t* cipher;
|
|
int nb_suites = picoquic_set_cipher_suite_list(selected_suites, cipher_suite_id);
|
|
if (nb_suites <= 0) {
|
|
cipher = NULL;
|
|
}
|
|
else {
|
|
cipher = selected_suites[0];
|
|
}
|
|
|
|
return cipher;
|
|
}
|
|
|
|
/* Obtain the SHA256 hash algorithm used to create secrets
|
|
*/
|
|
ptls_hash_algorithm_t* picoquic_get_openssl_sha256()
|
|
{
|
|
return &ptls_openssl_sha256;
|
|
}
|
|
|
|
/* Setting the supported key exchange algorithms,
|
|
using definitions in openSSL */
|
|
|
|
ptls_key_exchange_algorithm_t* picoquic_key_exchanges[] = { &ptls_openssl_secp256r1,
|
|
#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
& ptls_openssl_x25519,
|
|
#endif
|
|
NULL };
|
|
|
|
ptls_key_exchange_algorithm_t* picoquic_key_secp256r1[] = { &ptls_openssl_secp256r1, NULL };
|
|
|
|
#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
ptls_key_exchange_algorithm_t* picoquic_key_x25519[] = { &ptls_openssl_x25519, NULL };
|
|
#endif
|
|
|
|
|
|
static int picoquic_openssl_set_key_exchange_in_ctx(ptls_context_t* ctx, int key_exchange_id)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ctx == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
switch (key_exchange_id) {
|
|
case 0:
|
|
ctx->key_exchanges = picoquic_key_exchanges;
|
|
break;
|
|
case 20:
|
|
#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
ctx->key_exchanges = picoquic_key_x25519;
|
|
break;
|
|
#else
|
|
ret = -1;
|
|
break;
|
|
#endif
|
|
case 128:
|
|
ctx->key_exchanges = picoquic_key_secp256r1;
|
|
break;
|
|
case 256:
|
|
ctx->key_exchanges = picoquic_key_secp256r1;
|
|
break;
|
|
default:
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* This function is only used as part of the ESNI code.
|
|
* The purpose is to obtain an "exchange context" that matches the type of key found in the
|
|
* file defining the ESNI key. It does not really belong in the basic "crypto provider"
|
|
* API, but rather in the API that handles private keys, etc.
|
|
* TODO: rewrite this as a function that uses generic APIs.
|
|
*/
|
|
static int picoquic_openssl_exchange_context_from_file(char const* esni_key_file_name,
|
|
ptls_key_exchange_context_t** p_exchange_ctx)
|
|
{
|
|
int ret = 0;
|
|
EVP_PKEY* pkey = NULL;
|
|
BIO* bio = BIO_new_file(esni_key_file_name, "rb");
|
|
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
|
if (pkey == NULL) {
|
|
DBG_PRINTF("%s", "failed to load private key");
|
|
ret = PICOQUIC_ERROR_INVALID_FILE;
|
|
}
|
|
else {
|
|
*p_exchange_ctx = (ptls_key_exchange_context_t*)malloc(sizeof(ptls_key_exchange_context_t));
|
|
if (*p_exchange_ctx == NULL) {
|
|
DBG_PRINTF("%s", "no memory for ESNI private key\n");
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else if ((ret = ptls_openssl_create_key_exchange(p_exchange_ctx, pkey)) != 0) {
|
|
DBG_PRINTF("failed to load private key from file:%s:picotls-error:%d", esni_key_file_name, ret);
|
|
ret = PICOQUIC_ERROR_INVALID_FILE;
|
|
free(*p_exchange_ctx);
|
|
*p_exchange_ctx = NULL;
|
|
}
|
|
EVP_PKEY_free(pkey);
|
|
}
|
|
BIO_free(bio);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Provide a certificate signature function, based on the implementation in openssl.
|
|
*/
|
|
static int set_openssl_sign_certificate_from_key(EVP_PKEY* pkey, ptls_context_t* ctx)
|
|
{
|
|
int ret = 0;
|
|
ptls_openssl_sign_certificate_t* signer;
|
|
|
|
signer = (ptls_openssl_sign_certificate_t*)malloc(sizeof(ptls_openssl_sign_certificate_t));
|
|
|
|
if (signer == NULL || pkey == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = ptls_openssl_init_sign_certificate(signer, pkey);
|
|
ctx->sign_certificate = &signer->super;
|
|
}
|
|
|
|
if (pkey != NULL) {
|
|
EVP_PKEY_free(pkey);
|
|
}
|
|
|
|
if (ret != 0 && signer != NULL) {
|
|
free(signer);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_set_tls_key_openssl(ptls_context_t* ctx, const uint8_t* data, size_t len)
|
|
{
|
|
if (ctx->sign_certificate != NULL) {
|
|
ptls_openssl_dispose_sign_certificate((ptls_openssl_sign_certificate_t*)ctx->sign_certificate);
|
|
ctx->sign_certificate = NULL;
|
|
}
|
|
|
|
return set_openssl_sign_certificate_from_key(d2i_AutoPrivateKey(NULL, &data, (long)len), ctx);
|
|
}
|
|
|
|
/* Read a private key from file using openSSL */
|
|
static uint8_t* get_openssl_private_key_from_key_file(char const* file_name, int * key_length)
|
|
{
|
|
unsigned char* key_der;
|
|
unsigned char* tmp;
|
|
int length;
|
|
|
|
BIO* bio_key = BIO_new_file(file_name, "rb");
|
|
/* Load key and convert to DER */
|
|
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio_key, NULL, NULL, NULL);
|
|
length = i2d_PrivateKey(key, NULL);
|
|
key_der = (unsigned char*)malloc(length);
|
|
tmp = key_der;
|
|
i2d_PrivateKey(key, &tmp);
|
|
EVP_PKEY_free(key);
|
|
BIO_free(bio_key);
|
|
|
|
*key_length = length;
|
|
return key_der;
|
|
}
|
|
|
|
/* Set the certificate signature function and context using openSSL
|
|
*/
|
|
|
|
static int set_openssl_private_key_from_key_file(char const* keypem, ptls_context_t* ctx)
|
|
{
|
|
int ret = 0;
|
|
BIO* bio = BIO_new_file(keypem, "rb");
|
|
EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
|
if (pkey == NULL) {
|
|
DBG_PRINTF("%s", "failed to load private key");
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = set_openssl_sign_certificate_from_key(pkey, ctx);
|
|
}
|
|
BIO_free(bio);
|
|
return ret;
|
|
}
|
|
|
|
/* Read certificates from a file using openSSL functions
|
|
* TODO: what if we need to read multiple certificates for the chain?
|
|
*/
|
|
static ptls_iovec_t* picoquic_openssl_get_certs_from_file(char const * file_name, size_t * count)
|
|
{
|
|
BIO* bio_key = BIO_new_file(file_name, "rb");
|
|
/* Load cert and convert to DER */
|
|
X509* cert = PEM_read_bio_X509(bio_key, NULL, NULL, NULL);
|
|
int length = i2d_X509(cert, NULL);
|
|
unsigned char* cert_der = (unsigned char*)malloc(length);
|
|
unsigned char* tmp = cert_der;
|
|
i2d_X509(cert, &tmp);
|
|
X509_free(cert);
|
|
BIO_free(bio_key);
|
|
|
|
ptls_iovec_t* chain = malloc(sizeof(ptls_iovec_t));
|
|
if (chain == NULL) {
|
|
*count = 0;
|
|
} else {
|
|
*count = 1;
|
|
chain[0] = ptls_iovec_init(cert_der, length);
|
|
}
|
|
return chain;
|
|
}
|
|
|
|
/* Clear certificate objects allocated via openssl for a certificate
|
|
*/
|
|
static void picoquic_openssl_dispose_sign_certificate(ptls_sign_certificate_t* cert)
|
|
{
|
|
ptls_openssl_dispose_sign_certificate((ptls_openssl_sign_certificate_t*)cert);
|
|
}
|
|
|
|
/* Use openssl functions to create a certficate verifier */
|
|
ptls_openssl_verify_certificate_t* picoquic_openssl_get_certificate_verifier(char const * cert_root_file_name,
|
|
unsigned int * is_cert_store_not_empty)
|
|
{
|
|
ptls_openssl_verify_certificate_t * verifier = (ptls_openssl_verify_certificate_t*)malloc(sizeof(ptls_openssl_verify_certificate_t));
|
|
if (verifier != NULL) {
|
|
X509_STORE* store = X509_STORE_new();
|
|
|
|
if (cert_root_file_name != NULL && store != NULL) {
|
|
int file_ret = 0;
|
|
X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
|
if ((file_ret = X509_LOOKUP_load_file(lookup, cert_root_file_name, X509_FILETYPE_PEM)) != 1) {
|
|
DBG_PRINTF("Cannot load X509 store (%s), ret = %d\n",
|
|
cert_root_file_name, file_ret);
|
|
}
|
|
else {
|
|
*is_cert_store_not_empty = 1;
|
|
}
|
|
}
|
|
|
|
ptls_openssl_init_verify_certificate(verifier, store);
|
|
|
|
// If we created an instance of the store, release our reference after giving it to the verify_certificate callback.
|
|
// The callback internally increased the reference counter by one.
|
|
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
|
if (store != NULL) {
|
|
X509_STORE_free(store);
|
|
}
|
|
#endif
|
|
}
|
|
return verifier;
|
|
}
|
|
|
|
/* Set the list of root certificates used by the client.
|
|
* This implementation is specific to OpenSSL, because it is tied to the
|
|
* implementation of the verify certificate function. */
|
|
|
|
int picoquic_openssl_set_tls_root_certificates(picoquic_quic_t* quic, ptls_iovec_t* certs, size_t count)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
ptls_openssl_verify_certificate_t* verify_ctx = (ptls_openssl_verify_certificate_t*)ctx->verify_certificate;
|
|
|
|
for (size_t i = 0; i < count; ++i) {
|
|
X509* cert = d2i_X509(NULL, (const uint8_t**)&certs[i].base, (long)certs[i].len);
|
|
|
|
if (cert == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (X509_STORE_add_cert(verify_ctx->cert_store, cert) == 0) {
|
|
X509_free(cert);
|
|
return -2;
|
|
}
|
|
|
|
quic->is_cert_store_not_empty = 1;
|
|
|
|
X509_free(cert);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Explain OPENSSL errors */
|
|
int picoquic_open_ssl_explain_crypto_error(char const** err_file, int* err_line)
|
|
{
|
|
return ERR_get_error_line(err_file, err_line);
|
|
}
|
|
|
|
/* Clear the recorded errors in the crypto stack, e.g. before
|
|
* processing a new message.
|
|
*/
|
|
void picoquic_openssl_clear_crypto_errors()
|
|
{
|
|
ERR_clear_error();
|
|
}
|
|
#endif /* CRYPTO_PROVIDERS_REGION */
|
|
|
|
#define CRYPTO_PROVIDERS_API_REGION 1
|
|
|
|
#ifdef CRYPTO_PROVIDERS_API_REGION
|
|
/* Implementation of generic setup functions using the default present
|
|
* in this file. These functions may be declared in tls_api.h.
|
|
*/
|
|
|
|
void picoquic_init_crypto_provider()
|
|
{
|
|
picoquic_init_openssl();
|
|
}
|
|
|
|
/* Set the cryptographic random provider */
|
|
static void picoquic_set_random_provider_in_ctx(ptls_context_t* ctx)
|
|
{
|
|
ctx->random_bytes = ptls_openssl_random_bytes;
|
|
}
|
|
|
|
/* Set the cipher suites */
|
|
int picoquic_set_cipher_suite(picoquic_quic_t* quic, int cipher_suite_id)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
return (picoquic_set_cipher_suite_in_ctx(ctx, cipher_suite_id));
|
|
}
|
|
|
|
static ptls_cipher_suite_t* picoquic_get_cipher_suite_by_id(int cipher_suite_id)
|
|
{
|
|
return picoquic_get_selected_cipher_suite_by_id(cipher_suite_id);
|
|
}
|
|
|
|
/* Set the supported key exchange in the TLS context
|
|
* Supported algorithms are defined by keyexchange_id
|
|
* - 0: set all supported algorithms
|
|
* - 20: x25519
|
|
* - 128 or 256: secp256r1
|
|
*/
|
|
|
|
static int picoquic_set_key_exchange_in_ctx(ptls_context_t* ctx, int key_exchange_id)
|
|
{
|
|
return picoquic_openssl_set_key_exchange_in_ctx(ctx, key_exchange_id);
|
|
}
|
|
|
|
/* Set a key exchange from a file containing a private key.
|
|
* This is used for the implementation of ESNI.
|
|
* TODO: rewrite the ESNI code to use generic APIs.
|
|
*/
|
|
int picoquic_exchange_context_from_file(char const* key_file_name,
|
|
ptls_key_exchange_context_t** p_exchange_ctx)
|
|
{
|
|
return picoquic_openssl_exchange_context_from_file(key_file_name, p_exchange_ctx);
|
|
}
|
|
|
|
/* Obtain an AES128 ECB cipher, which is required for CID encryption
|
|
* according the CID for load balancer specification.
|
|
* TODO: rewrite this as a call to the generic "get cipher suite" API,
|
|
* then derive the ECB function from the selection of the AEAD function.
|
|
* This will obviate the need of providing a specific API.
|
|
*/
|
|
void* picoquic_aes128_ecb_create(int is_enc, const void* ecb_key)
|
|
{
|
|
return (void*)picoquic_aes128_ecb_openssl_create(is_enc, ecb_key);
|
|
}
|
|
|
|
/* Export hash functions so applications do not need to access picotls.
|
|
* It is not clear that these functions are actually used by applications.
|
|
* TODO: maybe reuse the "cipher suite" API, and just obtain the hash
|
|
* function in cipher suite 128 (SHA256) or 256 (SHA384).
|
|
*/
|
|
|
|
void* picoquic_hash_create(char const* algorithm_name) {
|
|
ptls_hash_context_t* ctx;
|
|
|
|
if (strcmp(algorithm_name, "SHA256") == 0) {
|
|
ctx = ptls_openssl_sha256.create();
|
|
}
|
|
else if (strcmp(algorithm_name, "SHA384") == 0) {
|
|
ctx = ptls_openssl_sha384.create();
|
|
}
|
|
else {
|
|
ctx = NULL;
|
|
}
|
|
|
|
return (void*)ctx;
|
|
}
|
|
|
|
size_t picoquic_hash_get_length(char const* algorithm_name) {
|
|
size_t len;
|
|
|
|
if (strcmp(algorithm_name, "SHA256") == 0) {
|
|
len = ptls_openssl_sha256.digest_size;
|
|
}
|
|
else if (strcmp(algorithm_name, "SHA384") == 0) {
|
|
len = ptls_openssl_sha384.digest_size;
|
|
}
|
|
else {
|
|
len = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/* Obtain the SHA256 hash, used to derive some secrets
|
|
*/
|
|
ptls_hash_algorithm_t* picoquic_get_sha256()
|
|
{
|
|
return picoquic_get_openssl_sha256();
|
|
}
|
|
|
|
void* picoquic_get_sha256_v()
|
|
{
|
|
return (void*)picoquic_get_sha256();
|
|
}
|
|
|
|
/* Get private key from current crypto processor */
|
|
uint8_t* picoquic_get_private_key_from_key_file(char const* file_name, int* key_length)
|
|
{
|
|
return get_openssl_private_key_from_key_file(file_name, key_length);
|
|
}
|
|
|
|
/* Set the certificate signing function in the context */
|
|
static int set_private_key_from_key_file(char const* keypem, ptls_context_t* ctx)
|
|
{
|
|
return set_openssl_private_key_from_key_file(keypem, ctx);
|
|
}
|
|
|
|
/* Read certificates from a file
|
|
*/
|
|
ptls_iovec_t* picoquic_get_certs_from_file(char const* file_name, size_t * count)
|
|
{
|
|
return picoquic_openssl_get_certs_from_file(file_name, count);
|
|
}
|
|
|
|
/* Clear certificate objects allocated by the crypto stack for a certficate
|
|
*/
|
|
void picoquic_dispose_sign_certificate(ptls_sign_certificate_t* cert)
|
|
{
|
|
picoquic_openssl_dispose_sign_certificate(cert);
|
|
}
|
|
|
|
/* Set the list of root certificates used by the client. */
|
|
int picoquic_set_tls_root_certificates(picoquic_quic_t* quic, ptls_iovec_t* certs, size_t count)
|
|
{
|
|
return picoquic_openssl_set_tls_root_certificates(quic, certs, count);
|
|
}
|
|
|
|
/* Set the TLS Key */
|
|
int picoquic_set_tls_key(picoquic_quic_t* quic, const uint8_t* data, size_t len)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
return picoquic_set_tls_key_openssl(ctx, data, len);
|
|
}
|
|
|
|
/* Return the certificate verifier callback provided by the crypto stack */
|
|
ptls_verify_certificate_t* picoquic_get_certificate_verifier(char const* cert_root_file_name,
|
|
unsigned int* is_cert_store_not_empty)
|
|
{
|
|
ptls_openssl_verify_certificate_t* verifier = picoquic_openssl_get_certificate_verifier(cert_root_file_name,
|
|
is_cert_store_not_empty);
|
|
return (verifier == NULL) ? NULL : &verifier->super;
|
|
}
|
|
|
|
|
|
/* Release a verify certificate callback function.
|
|
* TODO: there should be a delete function documented at the same time the
|
|
* callback is installed, to allow replacing one type of callback by another.
|
|
*/
|
|
|
|
void picoquic_dispose_certificate_verifier(ptls_verify_certificate_t* verifier) {
|
|
ptls_openssl_dispose_verify_certificate((ptls_openssl_verify_certificate_t*)verifier);
|
|
}
|
|
|
|
/* Provide a crypto provider independent interface to crypto errors.
|
|
* Can be called repeatedly until no error needs to be signalled.
|
|
*/
|
|
|
|
int picoquic_explain_crypto_error(char const** err_file, int* err_line)
|
|
{
|
|
return picoquic_open_ssl_explain_crypto_error(err_file, err_line);
|
|
}
|
|
|
|
/* Clear the recorded errors in the crypto stack, e.g. before
|
|
* processing a new message.
|
|
*/
|
|
void picoquic_clear_crypto_errors()
|
|
{
|
|
picoquic_openssl_clear_crypto_errors();
|
|
}
|
|
|
|
#endif /* CRYPTO_PROVIDERS_API_REGION */
|
|
|
|
#define CRYPTO_PROVIDERS_GENERIC_REGION 1
|
|
|
|
#ifdef CRYPTO_PROVIDERS_GENERIC_REGION
|
|
/* Generic APIs, derived from the APi to crypto providers */
|
|
|
|
|
|
/* Get the AES128GCM+SHA256 cipher suite required for Initial packets */
|
|
static ptls_cipher_suite_t* picoquic_get_aes128gcm_sha256()
|
|
{
|
|
return picoquic_get_cipher_suite_by_id(128);
|
|
}
|
|
|
|
void* picoquic_get_aes128gcm_sha256_v()
|
|
{
|
|
return (void*)picoquic_get_aes128gcm_sha256();
|
|
}
|
|
|
|
void* picoquic_get_aes128gcm_v()
|
|
{
|
|
void* aead = NULL;
|
|
ptls_cipher_suite_t* cipher = picoquic_get_aes128gcm_sha256();
|
|
|
|
if (cipher != NULL) {
|
|
aead = (void*)(cipher->aead);
|
|
}
|
|
return aead;
|
|
}
|
|
|
|
void* picoquic_get_cipher_suite_by_id_v(int cipher_suite_id)
|
|
{
|
|
return (void*)picoquic_get_cipher_suite_by_id(cipher_suite_id);
|
|
}
|
|
|
|
void picoquic_hash_update(uint8_t* input, size_t input_length, void* hash_context) {
|
|
((ptls_hash_context_t*)hash_context)->update((ptls_hash_context_t*)hash_context, input, input_length);
|
|
}
|
|
|
|
void picoquic_hash_finalize(uint8_t* output, void* hash_context) {
|
|
((ptls_hash_context_t*)hash_context)->final((ptls_hash_context_t*)hash_context, output, PTLS_HASH_FINAL_MODE_FREE);
|
|
}
|
|
|
|
#endif /* CRYPTO_PROVIDERS_GENERIC_REGION */
|
|
|
|
static void picoquic_setup_cleartext_aead_salt(size_t version_index, ptls_iovec_t* salt);
|
|
|
|
static void picoquic_free_log_event(picoquic_quic_t* quic);
|
|
|
|
void picoquic_log_crypto_errors(picoquic_cnx_t* cnx, int ret)
|
|
{
|
|
unsigned long openssl_err;
|
|
char const* err_file = NULL;
|
|
int err_line = 0;
|
|
|
|
while ((openssl_err = picoquic_explain_crypto_error(&err_file, &err_line)) != 0) {
|
|
picoquic_log_app_message(cnx, "OpenSSL error: %lu, file %s, line %d", openssl_err,
|
|
(err_file == NULL) ? "?" : err_file, err_line);
|
|
}
|
|
|
|
picoquic_log_app_message(cnx, "Picotls returns error: %d (0x%x)", ret, ret);
|
|
}
|
|
|
|
|
|
int picoquic_server_setup_ticket_aead_contexts(picoquic_quic_t* quic,
|
|
ptls_context_t* tls_ctx,
|
|
const uint8_t* secret, size_t secret_length);
|
|
|
|
/*
|
|
* Provide access to transport received transport extension for
|
|
* logging purpose.
|
|
*/
|
|
void picoquic_provide_received_transport_extensions(picoquic_cnx_t* cnx,
|
|
uint8_t** ext_received,
|
|
size_t* ext_received_length,
|
|
int* ext_received_return,
|
|
int* client_mode)
|
|
{
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
|
|
*ext_received = ctx->ext_received;
|
|
*ext_received_length = ctx->ext_received_length;
|
|
*ext_received_return = ctx->ext_received_return;
|
|
*client_mode = ctx->client_mode;
|
|
}
|
|
|
|
/* Crypto random number generator */
|
|
|
|
void picoquic_crypto_random(picoquic_quic_t* quic, void* buf, size_t len)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
ctx->random_bytes(buf, len);
|
|
}
|
|
|
|
uint64_t picoquic_crypto_uniform_random(picoquic_quic_t* quic, uint64_t rnd_max)
|
|
{
|
|
uint64_t rnd;
|
|
uint64_t rnd_min = ((uint64_t)((int64_t)-1)) % rnd_max;
|
|
|
|
do {
|
|
picoquic_crypto_random(quic, &rnd, sizeof(rnd));
|
|
} while (rnd < rnd_min);
|
|
|
|
return rnd % rnd_max;
|
|
}
|
|
|
|
/*
|
|
* Non crypto public random generator. This is meant to provide good enough randomness
|
|
* without disclosing the state of the crypto random number generator. This is
|
|
* adequate for non critical random numbers, such as sequence numbers or padding.
|
|
*
|
|
* The following is an implementation of xorshift1024* suggested by Sebastiano Vigna,
|
|
* following the general xorshift design by George Marsaglia.
|
|
* The state must be seeded so that it is not everywhere zero.
|
|
*
|
|
* The seed operation gets 64 bits from the crypto random generator. We then run the
|
|
* generator 16 times to mix that input into the 1024 bits of seed[16].
|
|
*
|
|
* In order to provide a minimum of protection against casual analysis, we run
|
|
* an obfuscation step before providing the result. The obfuscation involves
|
|
* multiply by a constant modulo, then XOR the result with obfuscator again.
|
|
* The obfuscator changes each time the random generator is seeded.
|
|
*
|
|
* If we were really paranoid, we would want to break possible discovery by passing
|
|
* the seeding bits from the crypto random generator through SHA256 or something
|
|
* similar, so there would be really no way to get at the state of crypto random
|
|
* generator. The 16 rounds of the xorshift process give a pretty good hash, but
|
|
* that can probably be broken by linear analysis. Or at least we have no proof
|
|
* that it cannot be broken.
|
|
*/
|
|
|
|
static uint64_t public_random_seed[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
|
|
static int public_random_index = 0;
|
|
static const uint64_t public_random_multiplier = 1181783497276652981ull;
|
|
static uint64_t public_random_obfuscator = 0x5555555555555555ull;
|
|
|
|
static uint64_t picoquic_public_random_step(void)
|
|
{
|
|
uint64_t s1;
|
|
const uint64_t s0 = public_random_seed[public_random_index++];
|
|
public_random_index &= 15;
|
|
s1 = public_random_seed[public_random_index];
|
|
s1 ^= (s1 << 31); // a
|
|
s1 ^= (s1 >> 11); // b
|
|
s1 ^= (s0 ^ (s0 >> 30)); // c
|
|
public_random_seed[public_random_index] = s1;
|
|
return s1;
|
|
}
|
|
|
|
uint64_t picoquic_public_random_64(void)
|
|
{
|
|
uint64_t s1 = picoquic_public_random_step();
|
|
s1 *= public_random_multiplier;
|
|
s1 ^= public_random_obfuscator;
|
|
return s1;
|
|
}
|
|
|
|
void picoquic_public_random_seed_64(uint64_t seed, int reset)
|
|
{
|
|
if (reset) {
|
|
public_random_index = 0;
|
|
for (uint64_t i = 0; i < 16; i++) {
|
|
public_random_seed[i] = i + 1u;
|
|
}
|
|
public_random_obfuscator = 0x5555555555555555ull;
|
|
}
|
|
|
|
public_random_seed[public_random_index] ^= seed;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
(void)picoquic_public_random_step();
|
|
}
|
|
}
|
|
|
|
|
|
void picoquic_public_random_seed(picoquic_quic_t* quic)
|
|
{
|
|
uint64_t seed[3];
|
|
picoquic_crypto_random(quic, &seed, sizeof(seed));
|
|
|
|
picoquic_public_random_seed_64(seed[0], 0);
|
|
public_random_obfuscator = seed[1];
|
|
}
|
|
|
|
void picoquic_public_random(void* buf, size_t len)
|
|
{
|
|
uint8_t* x = buf;
|
|
|
|
while (len > 0) {
|
|
uint64_t y = picoquic_public_random_64();
|
|
for (int i = 0; i < 8 && len > 0; i++) {
|
|
*x++ = (uint8_t)(y & 255);
|
|
y >>= 8;
|
|
len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t picoquic_public_uniform_random(uint64_t rnd_max)
|
|
{
|
|
uint64_t rnd;
|
|
uint64_t rnd_min = UINT64_MAX % rnd_max;
|
|
|
|
do {
|
|
rnd = picoquic_public_random_64();
|
|
} while (rnd < rnd_min);
|
|
|
|
return rnd % rnd_max;
|
|
}
|
|
|
|
/*
|
|
* The collect extensions call back is called by the picotls stack upon
|
|
* reception of a handshake message containing extensions. It should return true (1)
|
|
* if the stack can process the extension, false (0) otherwise.
|
|
*/
|
|
|
|
int picoquic_tls_collect_extensions_cb(ptls_t* tls, struct st_ptls_handshake_properties_t* properties, uint16_t type)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(tls);
|
|
UNREFERENCED_PARAMETER(properties);
|
|
#endif
|
|
return type == PICOQUIC_TRANSPORT_PARAMETERS_TLS_EXTENSION;
|
|
}
|
|
|
|
void picoquic_tls_set_extensions(picoquic_cnx_t* cnx, picoquic_tls_ctx_t* tls_ctx)
|
|
{
|
|
size_t consumed;
|
|
int ret = picoquic_prepare_transport_extensions(cnx, (tls_ctx->client_mode) ? 0 : 1,
|
|
tls_ctx->ext_data, sizeof(tls_ctx->ext_data), &consumed);
|
|
|
|
if (ret == 0) {
|
|
tls_ctx->ext[0].type = PICOQUIC_TRANSPORT_PARAMETERS_TLS_EXTENSION;
|
|
tls_ctx->ext[0].data.base = tls_ctx->ext_data;
|
|
tls_ctx->ext[0].data.len = consumed;
|
|
tls_ctx->ext[1].type = 0xFFFF;
|
|
tls_ctx->ext[1].data.base = NULL;
|
|
tls_ctx->ext[1].data.len = 0;
|
|
} else {
|
|
tls_ctx->ext[0].type = 0xFFFF;
|
|
tls_ctx->ext[0].data.base = NULL;
|
|
tls_ctx->ext[0].data.len = 0;
|
|
}
|
|
|
|
tls_ctx->handshake_properties.additional_extensions = tls_ctx->ext;
|
|
}
|
|
|
|
/*
|
|
* The collected extensions call back is called by the stack upon
|
|
* reception of a handshake message containing supported extensions.
|
|
*/
|
|
|
|
int picoquic_tls_collected_extensions_cb(ptls_t* tls, ptls_handshake_properties_t* properties,
|
|
ptls_raw_extension_t* slots)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(tls);
|
|
#endif
|
|
int ret = 0;
|
|
size_t consumed = 0;
|
|
/* Find the context from the TLS context */
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)((char*)properties - offsetof(struct st_picoquic_tls_ctx_t, handshake_properties));
|
|
|
|
for (int i_slot = 0; slots[i_slot].type != 0xFFFF; i_slot++) {
|
|
if (slots[i_slot].type == PICOQUIC_TRANSPORT_PARAMETERS_TLS_EXTENSION) {
|
|
size_t copied_length = sizeof(ctx->ext_received);
|
|
|
|
/* Retrieve the transport parameters */
|
|
ret = picoquic_receive_transport_extensions(ctx->cnx, (ctx->client_mode) ? 1 : 0,
|
|
slots[i_slot].data.base, slots[i_slot].data.len, &consumed);
|
|
|
|
/* Copy the extensions in the local context for further debugging */
|
|
ctx->ext_received_length = slots[i_slot].data.len;
|
|
if (copied_length > ctx->ext_received_length)
|
|
copied_length = ctx->ext_received_length;
|
|
memcpy(ctx->ext_received, slots[i_slot].data.base, copied_length);
|
|
ctx->ext_received_return = ret;
|
|
/* For now, override the value in case of default */
|
|
ret = 0;
|
|
|
|
/* In server mode, only compose the extensions if properly received from client */
|
|
if (ctx->client_mode == 0) {
|
|
picoquic_tls_set_extensions(ctx->cnx, ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The Hello Call Back is called on the server side upon reception of the
|
|
* Client Hello. The picotls code will parse the client hello and retrieve
|
|
* parameters such as SNI and proposed ALPN.
|
|
* TODO: check the SNI in case several are supported.
|
|
* TODO: check the ALPN in case several are supported.
|
|
*/
|
|
|
|
int picoquic_client_hello_call_back(ptls_on_client_hello_t* on_hello_cb_ctx,
|
|
ptls_t* tls, ptls_on_client_hello_parameters_t *params)
|
|
{
|
|
const uint8_t * alpn_found = 0;
|
|
size_t alpn_found_length = 0;
|
|
int ret = 0;
|
|
picoquic_quic_t** ppquic = (picoquic_quic_t**)(((char*)on_hello_cb_ctx) + sizeof(ptls_on_client_hello_t));
|
|
picoquic_quic_t* quic = *ppquic;
|
|
|
|
/* Save the server name */
|
|
ptls_set_server_name(tls, (const char *)params->server_name.base, params->server_name.len);
|
|
|
|
#ifdef PTLS_ESNI_NONCE_SIZE
|
|
if (params->esni && quic->cnx_in_progress != NULL) {
|
|
/* Find the ESNI secret if any, and copy key values to picoquic tls context */
|
|
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)quic->cnx_in_progress->tls_ctx;
|
|
struct st_ptls_esni_secret_t * esni = ptls_get_esni_secret(tls_ctx->tls);
|
|
if (esni != NULL) {
|
|
tls_ctx->esni_version = esni->version;
|
|
memcpy(tls_ctx->esni_nonce, esni->nonce, PTLS_ESNI_NONCE_SIZE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Check if the client is proposing the expected ALPN */
|
|
if (quic->default_alpn != NULL) {
|
|
size_t len = strlen(quic->default_alpn);
|
|
|
|
for (size_t i = 0; i < params->negotiated_protocols.count; i++) {
|
|
if (params->negotiated_protocols.list[i].len == len && memcmp(params->negotiated_protocols.list[i].base, quic->default_alpn, len) == 0) {
|
|
if (quic->cnx_in_progress != NULL) {
|
|
picoquic_log_app_message(quic->cnx_in_progress, "ALPN[%d] matches default alpn (%s)", (int)i, quic->default_alpn);
|
|
}
|
|
alpn_found = (const uint8_t *)quic->default_alpn;
|
|
alpn_found_length = len;
|
|
ptls_set_negotiated_protocol(tls, quic->default_alpn, len);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (quic->alpn_select_fn != NULL) {
|
|
size_t selected = quic->alpn_select_fn(quic, params->negotiated_protocols.list, params->negotiated_protocols.count);
|
|
|
|
if (selected < params->negotiated_protocols.count) {
|
|
alpn_found = params->negotiated_protocols.list[selected].base;
|
|
alpn_found_length = params->negotiated_protocols.list[selected].len;
|
|
ptls_set_negotiated_protocol(tls, (const char *)params->negotiated_protocols.list[selected].base, params->negotiated_protocols.list[selected].len);
|
|
}
|
|
}
|
|
|
|
if (quic->cnx_in_progress != NULL) {
|
|
picoquic_log_negotiated_alpn(quic->cnx_in_progress,
|
|
0, params->server_name.base, params->server_name.len, alpn_found, alpn_found_length,
|
|
params->negotiated_protocols.list, params->negotiated_protocols.count);
|
|
}
|
|
|
|
/* ALPN is mandatory in Quic. Return an error if no match found. */
|
|
if (alpn_found == NULL) {
|
|
ret = PTLS_ALERT_NO_APPLICATION_PROTOCOL;
|
|
}
|
|
|
|
if (ret != 0 && quic->cnx_in_progress != NULL) {
|
|
picoquic_log_app_message(quic->cnx_in_progress, "Client Hello call back returns %d (0x%x)", ret, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The server will generate session tickets if some parameters are set in the server
|
|
* TLS context, including:
|
|
* - the session ticket encryption callback, defined per the "encrypt_ticket" member of the context.
|
|
* - the session ticket lifetime, defined per the "ticket_life_time" member of the context.
|
|
* The encrypt call back is called on the server side when a session resume ticket is ready.
|
|
* The call is:
|
|
* cb(tls->ctx->encrypt_ticket, tls, 1, sendbuf,
|
|
* ptls_iovec_init(session_id.base, session_id.off))
|
|
* The call to decrypt is:
|
|
* tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)
|
|
* Should return 0 if the ticket is good, etc.
|
|
*/
|
|
|
|
int picoquic_server_encrypt_ticket_call_back(ptls_encrypt_ticket_t* encrypt_ticket_ctx,
|
|
ptls_t* tls, int is_encrypt, ptls_buffer_t* dst, ptls_iovec_t src)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(tls);
|
|
#endif
|
|
|
|
/* Assume that the keys are in the quic context
|
|
* The tickets are composed of a 64 bit "sequence number"
|
|
* followed by the result of the clear text encryption.
|
|
*/
|
|
int ret = 0;
|
|
picoquic_quic_t** ppquic = (picoquic_quic_t**)(((char*)encrypt_ticket_ctx) + sizeof(ptls_encrypt_ticket_t));
|
|
picoquic_quic_t* quic = *ppquic;
|
|
|
|
if (is_encrypt != 0) {
|
|
ptls_aead_context_t* aead_enc = (ptls_aead_context_t*)quic->aead_encrypt_ticket_ctx;
|
|
/* Encoding*/
|
|
if (aead_enc == NULL) {
|
|
ret = -1;
|
|
} else if ((ret = ptls_buffer_reserve(dst, 8 + src.len + aead_enc->algo->tag_size)) == 0) {
|
|
/* Create and store the ticket sequence number */
|
|
uint64_t seq_num = picoquic_public_random_64();
|
|
picoformat_64(dst->base + dst->off, seq_num);
|
|
dst->off += 8;
|
|
/* Run AEAD encryption */
|
|
dst->off += ptls_aead_encrypt(aead_enc, dst->base + dst->off,
|
|
src.base, src.len, seq_num, NULL, 0);
|
|
}
|
|
} else {
|
|
ptls_aead_context_t* aead_dec = (ptls_aead_context_t*)quic->aead_decrypt_ticket_ctx;
|
|
/* Encoding*/
|
|
if (aead_dec == NULL) {
|
|
ret = -1;
|
|
} else if (src.len < 8 + aead_dec->algo->tag_size) {
|
|
ret = -1;
|
|
} else if ((ret = ptls_buffer_reserve(dst, src.len)) == 0) {
|
|
/* Decode the ticket sequence number */
|
|
uint64_t seq_num = PICOPARSE_64(src.base);
|
|
/* Decrypt */
|
|
size_t decrypted = ptls_aead_decrypt(aead_dec, dst->base + dst->off,
|
|
src.base + 8, src.len - 8, seq_num, NULL, 0);
|
|
|
|
if (decrypted > src.len - 8) {
|
|
/* decryption error */
|
|
ret = -1;
|
|
if (quic->F_log != NULL) {
|
|
picoquic_log_app_message(quic->cnx_in_progress, "%s",
|
|
"Session ticket could not be decrypted");
|
|
}
|
|
} else {
|
|
dst->off += decrypted;
|
|
if (quic->F_log != NULL) {
|
|
picoquic_log_app_message(quic->cnx_in_progress, "%s",
|
|
"Session ticket properly decrypted");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The client signals its willingness to receive session resume tickets by providing
|
|
* the "save ticket" callback in the client's quic context.
|
|
*/
|
|
|
|
int picoquic_client_save_ticket_call_back(ptls_save_ticket_t* save_ticket_ctx,
|
|
ptls_t* tls, ptls_iovec_t input)
|
|
{
|
|
int ret = 0;
|
|
picoquic_quic_t* quic = *((picoquic_quic_t**)(((char*)save_ticket_ctx) + sizeof(ptls_save_ticket_t)));
|
|
const char* sni = ptls_get_server_name(tls);
|
|
const char* alpn = ptls_get_negotiated_protocol(tls);
|
|
picoquic_cnx_t * cnx = (picoquic_cnx_t *)*ptls_get_data_ptr(tls);
|
|
|
|
if (alpn == NULL && quic != NULL) {
|
|
alpn = quic->default_alpn;
|
|
}
|
|
|
|
if (sni != NULL && alpn != NULL) {
|
|
ret = picoquic_store_ticket(&quic->p_first_ticket, 0, sni, (uint16_t)strlen(sni),
|
|
alpn, (uint16_t)strlen(alpn), input.base, (uint16_t)input.len, &cnx->remote_parameters);
|
|
} else {
|
|
DBG_PRINTF("Received incorrect session resume ticket, sni = %s, alpn = %s, length = %d\n",
|
|
(sni == NULL) ? "NULL" : sni, (alpn == NULL) ? "NULL" : alpn, (int)input.len);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Time get callback
|
|
*/
|
|
uint64_t picoquic_get_simulated_time_cb(ptls_get_time_t* self)
|
|
{
|
|
uint64_t** pp_simulated_time = (uint64_t**)(((char*)self) + sizeof(ptls_get_time_t));
|
|
return ((**pp_simulated_time) / 1000);
|
|
}
|
|
|
|
/*
|
|
* Verify certificate
|
|
*/
|
|
typedef struct {
|
|
ptls_verify_certificate_t cb;
|
|
picoquic_quic_t *quic;
|
|
} picoquic_verify_certificate_t;
|
|
|
|
typedef struct {
|
|
/* The pointer to the overlying `verify_ctx` */
|
|
void *verify_ctx;
|
|
int (*verify_sign)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t sign);
|
|
} picoquic_verify_ctx_t;
|
|
|
|
static int verify_sign_callback(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t sign)
|
|
{
|
|
picoquic_verify_ctx_t* ctx = (picoquic_verify_ctx_t*)verify_ctx;
|
|
int ret = 0;
|
|
|
|
ret = ctx->verify_sign(ctx->verify_ctx, data, sign);
|
|
|
|
free(ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int verify_certificate_callback(ptls_verify_certificate_t* _self, ptls_t* tls,
|
|
int (**verify_sign)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t sign),
|
|
void **verify_data,
|
|
ptls_iovec_t *certs,
|
|
size_t num_certs)
|
|
{
|
|
picoquic_verify_certificate_t *self = container_of(_self, picoquic_verify_certificate_t, cb);
|
|
picoquic_cnx_t* cnx = (picoquic_cnx_t*)*ptls_get_data_ptr(tls);
|
|
int ret = 0;
|
|
void *verify_ctx = NULL;
|
|
picoquic_verify_sign_cb_fn verify_sign_fn = NULL;
|
|
|
|
ret = (self->quic->verify_certificate_callback_fn)(self->quic->verify_certificate_ctx, cnx,
|
|
certs, num_certs, &verify_sign_fn, &verify_ctx);
|
|
|
|
if (ret == 0) {
|
|
*verify_sign = verify_sign_callback;
|
|
*verify_data = malloc(sizeof(picoquic_verify_ctx_t));
|
|
if (*verify_data != NULL) {
|
|
((picoquic_verify_ctx_t*)*verify_data)->verify_ctx = verify_ctx;
|
|
((picoquic_verify_ctx_t*)*verify_data)->verify_sign = verify_sign_fn;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_enable_custom_verify_certificate_callback(picoquic_quic_t* quic) {
|
|
picoquic_verify_certificate_t* verifier = NULL;
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
assert(quic->verify_certificate_callback_fn != NULL);
|
|
|
|
verifier = (picoquic_verify_certificate_t*)malloc(sizeof(picoquic_verify_certificate_t));
|
|
if (verifier == NULL) {
|
|
return PICOQUIC_ERROR_MEMORY;
|
|
} else {
|
|
verifier->quic = quic;
|
|
verifier->cb.cb = verify_certificate_callback;
|
|
ctx->verify_certificate = &verifier->cb;
|
|
quic->is_cert_store_not_empty = 1;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
void picoquic_dispose_verify_certificate_callback(picoquic_quic_t* quic, int custom) {
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
if (ctx->verify_certificate == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (custom == 0) {
|
|
picoquic_dispose_certificate_verifier(ctx->verify_certificate);
|
|
}
|
|
free(ctx->verify_certificate);
|
|
|
|
ctx->verify_certificate = NULL;
|
|
}
|
|
|
|
/* set key from secret: this is used to create AEAD contexts and PN encoding contexts
|
|
* after a key update callback, and also to create the initial keys from a locally
|
|
* computed secret
|
|
*/
|
|
static int picoquic_set_aead_from_secret(void ** v_aead,ptls_cipher_suite_t * cipher, int is_enc, const void *secret)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (*v_aead != NULL) {
|
|
ptls_aead_free((ptls_aead_context_t*)*v_aead);
|
|
}
|
|
|
|
if ((*v_aead = ptls_aead_new(cipher->aead, cipher->hash, is_enc, secret, PICOQUIC_LABEL_QUIC_KEY_BASE)) == NULL) {
|
|
ret = PTLS_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int picoquic_set_pn_enc_from_secret(void ** v_pn_enc, ptls_cipher_suite_t * cipher, int is_enc, const void *secret)
|
|
{
|
|
uint8_t pnekey[PTLS_MAX_SECRET_SIZE];
|
|
int ret;
|
|
|
|
if (*v_pn_enc != NULL) {
|
|
ptls_cipher_free((ptls_cipher_context_t *)*v_pn_enc);
|
|
*v_pn_enc = NULL;
|
|
}
|
|
|
|
if ((ret = ptls_hkdf_expand_label(cipher->hash, pnekey,
|
|
cipher->aead->ctr_cipher->key_size, ptls_iovec_init(secret, cipher->hash->digest_size),
|
|
PICOQUIC_LABEL_HP, ptls_iovec_init(NULL, 0), PICOQUIC_LABEL_QUIC_KEY_BASE)) == 0) {
|
|
if ((*v_pn_enc = ptls_cipher_new(cipher->aead->ctr_cipher, is_enc, pnekey)) == NULL) {
|
|
ret = PTLS_ERROR_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_aes128_ecb_free(void * v_aesecb)
|
|
{
|
|
ptls_cipher_free((ptls_cipher_context_t *)v_aesecb);
|
|
}
|
|
|
|
void picoquic_aes128_ecb_encrypt(void* v_aesecb, uint8_t * output, const uint8_t * input, size_t len)
|
|
{
|
|
ptls_cipher_encrypt((ptls_cipher_context_t*)v_aesecb, output, input, len);
|
|
}
|
|
|
|
static int picoquic_set_key_from_secret(ptls_cipher_suite_t * cipher, int is_enc, int is_rotation, picoquic_crypto_context_t * ctx, const void *secret)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (is_enc != 0) {
|
|
ret = picoquic_set_aead_from_secret(&ctx->aead_encrypt, cipher, is_enc, secret);
|
|
|
|
if (ret == 0 && !is_rotation) {
|
|
ret = picoquic_set_pn_enc_from_secret(&ctx->pn_enc, cipher, is_enc, secret);
|
|
}
|
|
} else {
|
|
ret = picoquic_set_aead_from_secret(&ctx->aead_decrypt, cipher, is_enc, secret);
|
|
|
|
if (ret == 0 && !is_rotation) {
|
|
ret = picoquic_set_pn_enc_from_secret(&ctx->pn_dec, cipher, is_enc, secret);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Key update callback: this is called by TLS whenever the session key has changed,
|
|
* from the function "setup_traffic_protection" in picotls.c.
|
|
*
|
|
* The macro generated callback struct is:
|
|
* typedef struct st_ptls_update_traffic_key_t {
|
|
* ret (*cb)(struct st_ptls_update_traffic_key_t * self, ptls_t *tls, int is_enc, size_t epoch, const void *secret);
|
|
* } ptls_update_traffic_key_t;
|
|
*
|
|
* The parameters are defined as:
|
|
* - self -- classic callback structure in picotls, can be remapped to hold additional arguments.
|
|
* - tls -- the tls context of the connection
|
|
* - is_enc -- 0: decryption key, 1: decryption key
|
|
* - epoch -- 1: "c e traffic"
|
|
* -- 2: "s hs traffic"
|
|
* -- 2: "c hs traffic"
|
|
* -- 3: "s ap traffic"
|
|
* -- 3: "c ap traffic"
|
|
* - secret -- the expansion of the master secret with the label specific to the key epoch
|
|
* and client or server mode.
|
|
*/
|
|
|
|
typedef struct st_picoquic_update_traffic_key_t {
|
|
int(*cb)(struct st_ptls_update_traffic_key_t * self, ptls_t *tls, int is_enc, size_t epoch, const void *secret);
|
|
picoquic_cnx_t *cnx;
|
|
} picoquic_update_traffic_key_t;
|
|
|
|
static int picoquic_update_traffic_key_callback(ptls_update_traffic_key_t * self, ptls_t *tls, int is_enc, size_t epoch, const void *secret)
|
|
{
|
|
picoquic_cnx_t* cnx = (picoquic_cnx_t*)*ptls_get_data_ptr(tls);
|
|
picoquic_tls_ctx_t * tls_ctx = (picoquic_tls_ctx_t *)cnx->tls_ctx;
|
|
ptls_context_t* ctx = (ptls_context_t*)cnx->quic->tls_master_ctx;
|
|
ptls_cipher_suite_t * cipher = ptls_get_cipher(tls);
|
|
UNREFERENCED_PARAMETER(self);
|
|
|
|
int ret = picoquic_set_key_from_secret(cipher, is_enc, 0, &cnx->crypto_context[epoch], secret);
|
|
if (cnx->cnx_state < picoquic_state_ready) {
|
|
cnx->recycle_sooner_needed = 1;
|
|
}
|
|
|
|
if (ret == 0 && epoch == 3) {
|
|
memcpy((is_enc) ? tls_ctx->app_secret_enc : tls_ctx->app_secret_dec, secret, cipher->hash->digest_size);
|
|
}
|
|
|
|
if (ctx->log_event != NULL) {
|
|
char hexbuf[PTLS_MAX_DIGEST_SIZE * 2 + 1];
|
|
static const char *log_labels[2][4] = {
|
|
{NULL, "CLIENT_EARLY_TRAFFIC_SECRET", "CLIENT_HANDSHAKE_TRAFFIC_SECRET", "CLIENT_TRAFFIC_SECRET_0"},
|
|
{NULL, NULL, "SERVER_HANDSHAKE_TRAFFIC_SECRET", "SERVER_TRAFFIC_SECRET_0"}};
|
|
const char *secret_label = log_labels[ptls_is_server(tls) == is_enc][epoch];
|
|
ptls_hexdump(hexbuf, secret, cipher->hash->digest_size);
|
|
ctx->log_event->cb(ctx->log_event, tls, secret_label, "%s", hexbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ptls_update_traffic_key_t * picoquic_set_update_traffic_key_callback() {
|
|
ptls_update_traffic_key_t * cb_st = (ptls_update_traffic_key_t *)malloc(sizeof(ptls_update_traffic_key_t));
|
|
|
|
if (cb_st != NULL) {
|
|
memset(cb_st, 0, sizeof(ptls_update_traffic_key_t));
|
|
cb_st->cb = picoquic_update_traffic_key_callback;
|
|
}
|
|
|
|
return cb_st;
|
|
}
|
|
|
|
int picoquic_setup_initial_master_secret(
|
|
ptls_cipher_suite_t * cipher,
|
|
ptls_iovec_t salt,
|
|
picoquic_connection_id_t initial_cnxid,
|
|
uint8_t * master_secret)
|
|
{
|
|
int ret = 0;
|
|
ptls_iovec_t ikm;
|
|
uint8_t cnx_id_serialized[PICOQUIC_CONNECTION_ID_MAX_SIZE];
|
|
|
|
ikm.len = picoquic_format_connection_id(cnx_id_serialized, PICOQUIC_CONNECTION_ID_MAX_SIZE,
|
|
initial_cnxid);
|
|
ikm.base = cnx_id_serialized;
|
|
|
|
/* Extract the master key -- key length will be 32 per SHA256 */
|
|
ret = ptls_hkdf_extract(cipher->hash, master_secret, salt, ikm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_setup_initial_secrets(
|
|
ptls_cipher_suite_t * cipher,
|
|
uint8_t * master_secret,
|
|
uint8_t * client_secret,
|
|
uint8_t * server_secret)
|
|
{
|
|
int ret = 0;
|
|
ptls_iovec_t prk;
|
|
|
|
prk.base = master_secret;
|
|
prk.len = cipher->hash->digest_size;
|
|
|
|
/* Get the client secret */
|
|
ret = ptls_hkdf_expand_label(cipher->hash, client_secret, cipher->hash->digest_size,
|
|
prk, PICOQUIC_LABEL_INITIAL_CLIENT, ptls_iovec_init(NULL, 0), NULL);
|
|
|
|
if (ret == 0) {
|
|
/* Get the server secret */
|
|
ret = ptls_hkdf_expand_label(cipher->hash, server_secret, cipher->hash->digest_size,
|
|
prk, PICOQUIC_LABEL_INITIAL_SERVER, ptls_iovec_init(NULL, 0), NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_setup_initial_traffic_keys(picoquic_cnx_t* cnx)
|
|
{
|
|
int ret = 0;
|
|
uint8_t master_secret[256]; /* secret_max */
|
|
ptls_cipher_suite_t * cipher = picoquic_get_aes128gcm_sha256();
|
|
ptls_iovec_t salt;
|
|
uint8_t client_secret[256];
|
|
uint8_t server_secret[256];
|
|
uint8_t *secret1, *secret2;
|
|
|
|
if (cipher == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
picoquic_setup_cleartext_aead_salt(cnx->version_index, &salt);
|
|
|
|
/* Extract the master key -- key length will be 32 per SHA256 */
|
|
ret = picoquic_setup_initial_master_secret(cipher, salt, cnx->initial_cnxid, master_secret);
|
|
}
|
|
|
|
/* set up client and server secrets */
|
|
if (ret == 0) {
|
|
ret = picoquic_setup_initial_secrets(cipher, master_secret, client_secret, server_secret);
|
|
}
|
|
|
|
/* derive the initial keys */
|
|
if (ret == 0) {
|
|
if (!cnx->client_mode) {
|
|
secret1 = server_secret;
|
|
secret2 = client_secret;
|
|
}
|
|
else {
|
|
secret1 = client_secret;
|
|
secret2 = server_secret;
|
|
}
|
|
|
|
ret = picoquic_set_key_from_secret(cipher, 1, 0, &cnx->crypto_context[0], secret1);
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_set_key_from_secret(cipher, 0, 0, &cnx->crypto_context[0], secret2);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Key rotation.
|
|
*
|
|
* The old keys get moved to the old crypto context.
|
|
* The secrets are rotated.
|
|
* The new context gets informed.
|
|
*
|
|
* The key update is defined in RFC 8446 section 7.2 as:
|
|
* application_traffic_secret_N+1 =
|
|
* HKDF-Expand-Label(application_traffic_secret_N,
|
|
* "quic ku", "", Hash.length)
|
|
* Label: PICOQUIC_LABEL_TRAFFIC_UPDATE
|
|
*/
|
|
int picoquic_rotate_app_secret(ptls_cipher_suite_t * cipher, uint8_t * secret)
|
|
{
|
|
int ret = 0;
|
|
uint8_t new_secret[PTLS_MAX_DIGEST_SIZE];
|
|
|
|
ret = ptls_hkdf_expand_label(cipher->hash, new_secret,
|
|
cipher->hash->digest_size, ptls_iovec_init(secret, cipher->hash->digest_size), PICOQUIC_LABEL_TRAFFIC_UPDATE,
|
|
ptls_iovec_init(NULL, 0), PICOQUIC_LABEL_QUIC_BASE);
|
|
if (ret == 0) {
|
|
memcpy(secret, new_secret, cipher->hash->digest_size);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
uint8_t * picoquic_get_app_secret(picoquic_cnx_t* cnx, int is_enc)
|
|
{
|
|
picoquic_tls_ctx_t * tls_ctx = (picoquic_tls_ctx_t *)cnx->tls_ctx;
|
|
|
|
return (is_enc) ?tls_ctx->app_secret_enc:tls_ctx->app_secret_dec;
|
|
}
|
|
|
|
size_t picoquic_get_app_secret_size(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_tls_ctx_t * tls_ctx = (picoquic_tls_ctx_t *)cnx->tls_ctx;
|
|
|
|
ptls_cipher_suite_t * cipher = ptls_get_cipher(tls_ctx->tls);
|
|
|
|
return (cipher->hash->digest_size);
|
|
}
|
|
|
|
int picoquic_compute_new_rotated_keys(picoquic_cnx_t * cnx)
|
|
{
|
|
int ret = 0;
|
|
picoquic_tls_ctx_t * tls_ctx = (picoquic_tls_ctx_t *)cnx->tls_ctx;
|
|
ptls_cipher_suite_t * cipher = ptls_get_cipher(tls_ctx->tls);
|
|
|
|
/* Verify that the previous transition is complete */
|
|
if (cnx->crypto_context_new.aead_decrypt != NULL ||
|
|
cnx->crypto_context_new.aead_encrypt != NULL) {
|
|
if (cnx->crypto_context_new.aead_decrypt == NULL ||
|
|
cnx->crypto_context_new.aead_encrypt == NULL) {
|
|
ret = PICOQUIC_ERROR_CANNOT_COMPUTE_KEY;
|
|
}
|
|
else {
|
|
/* already computed */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Recompute the secrets */
|
|
if (ret == 0) {
|
|
ret = picoquic_rotate_app_secret(cipher, tls_ctx->app_secret_enc);
|
|
#ifdef _DEBUG
|
|
if (ret == 0) {
|
|
DBG_PRINTF("Rotated Encryption Secret (%d):\n", (int)cipher->hash->digest_size);
|
|
debug_dump(tls_ctx->app_secret_enc, (int)cipher->hash->digest_size);
|
|
}
|
|
else {
|
|
DBG_PRINTF("Encryption secret rotation fails, ret=%x\n", ret);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_set_key_from_secret(cipher, 1, 1, &cnx->crypto_context_new, tls_ctx->app_secret_enc);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_rotate_app_secret(cipher, tls_ctx->app_secret_dec);
|
|
#ifdef _DEBUG
|
|
if (ret == 0) {
|
|
DBG_PRINTF("Rotated Decryption Secret (%d):\n", (int)cipher->hash->digest_size);
|
|
debug_dump(tls_ctx->app_secret_dec, (int)cipher->hash->digest_size);
|
|
}
|
|
else {
|
|
DBG_PRINTF("Decryption secret rotation fails, ret=%x\n", ret);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_set_key_from_secret(cipher, 0, 1, &cnx->crypto_context_new, tls_ctx->app_secret_dec);
|
|
}
|
|
|
|
return (ret == 0)?0: PICOQUIC_ERROR_CANNOT_COMPUTE_KEY;
|
|
}
|
|
|
|
void picoquic_apply_rotated_keys(picoquic_cnx_t * cnx, int is_enc)
|
|
{
|
|
if (is_enc) {
|
|
if (cnx->crypto_context[3].aead_encrypt != NULL) {
|
|
ptls_aead_free((ptls_aead_context_t *)cnx->crypto_context[3].aead_encrypt);
|
|
}
|
|
|
|
cnx->crypto_context[3].aead_encrypt = cnx->crypto_context_new.aead_encrypt;
|
|
cnx->crypto_context_new.aead_encrypt = NULL;
|
|
|
|
cnx->key_phase_enc ^= 1;
|
|
picoquic_log_pn_dec_trial(cnx);
|
|
}
|
|
else {
|
|
if (cnx->crypto_context_old.aead_decrypt != NULL) {
|
|
ptls_aead_free((ptls_aead_context_t *)cnx->crypto_context_old.aead_decrypt);
|
|
}
|
|
|
|
cnx->crypto_context_old.aead_decrypt = cnx->crypto_context[3].aead_decrypt;
|
|
cnx->crypto_context[3].aead_decrypt = cnx->crypto_context_new.aead_decrypt;
|
|
cnx->crypto_context_new.aead_decrypt = NULL;
|
|
|
|
cnx->key_phase_dec ^= 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Release the crypto context, and the associated keys.
|
|
*/
|
|
|
|
void picoquic_crypto_context_free(picoquic_crypto_context_t * ctx)
|
|
{
|
|
if (ctx->aead_encrypt != NULL) {
|
|
ptls_aead_free((ptls_aead_context_t *)ctx->aead_encrypt);
|
|
ctx->aead_encrypt = NULL;
|
|
}
|
|
|
|
if (ctx->aead_decrypt != NULL) {
|
|
ptls_aead_free((ptls_aead_context_t *)ctx->aead_decrypt);
|
|
ctx->aead_decrypt = NULL;
|
|
}
|
|
|
|
if (ctx->pn_enc != NULL) {
|
|
ptls_cipher_free((ptls_cipher_context_t *)ctx->pn_enc);
|
|
ctx->pn_enc = NULL;
|
|
}
|
|
|
|
if (ctx->pn_dec != NULL) {
|
|
ptls_cipher_free((ptls_cipher_context_t *)ctx->pn_dec);
|
|
ctx->pn_dec = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setting the master TLS context.
|
|
* On servers, this implies setting the "on hello" call back
|
|
*/
|
|
|
|
int picoquic_master_tlscontext(picoquic_quic_t* quic,
|
|
char const* cert_file_name, char const* key_file_name, const char * cert_root_file_name,
|
|
const uint8_t* ticket_key, size_t ticket_key_length)
|
|
{
|
|
/* Create a client context or a server context */
|
|
int ret = 0;
|
|
ptls_context_t* ctx;
|
|
ptls_on_client_hello_t* och = NULL;
|
|
ptls_encrypt_ticket_t* encrypt_ticket = NULL;
|
|
ptls_save_ticket_t* save_ticket = NULL;
|
|
unsigned int is_cert_store_not_empty = 0;
|
|
|
|
picoquic_init_crypto_provider(); /* For example, init openSSL if in use. */
|
|
|
|
ctx = (ptls_context_t*)malloc(sizeof(ptls_context_t));
|
|
|
|
if (ctx == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
memset(ctx, 0, sizeof(ptls_context_t));
|
|
picoquic_set_random_provider_in_ctx(ctx);
|
|
|
|
ret = picoquic_set_key_exchange_in_ctx(ctx, 0); /* was: ctx->key_exchanges = picoquic_key_exchanges; */
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_set_cipher_suite_in_ctx(ctx, 0); /* was: ptls_openssl_cipher_suites; */
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ctx->send_change_cipher_spec = 0;
|
|
|
|
ctx->hkdf_label_prefix__obsolete = NULL;
|
|
ctx->update_traffic_key = picoquic_set_update_traffic_key_callback();
|
|
|
|
if (quic->p_simulated_time == NULL) {
|
|
ctx->get_time = &ptls_get_time;
|
|
}
|
|
else {
|
|
ptls_get_time_t* time_getter = (ptls_get_time_t*)malloc(sizeof(ptls_get_time_t) + sizeof(uint64_t*));
|
|
if (time_getter == NULL) {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else {
|
|
uint64_t** pp_simulated_time = (uint64_t**)(((char*)time_getter) + sizeof(ptls_get_time_t));
|
|
|
|
time_getter->cb = picoquic_get_simulated_time_cb;
|
|
*pp_simulated_time = quic->p_simulated_time;
|
|
ctx->get_time = time_getter;
|
|
}
|
|
}
|
|
|
|
if (cert_file_name != NULL && key_file_name != NULL) {
|
|
/* Read the certificate file */
|
|
if (ptls_load_certificates(ctx, (char*)cert_file_name) != 0) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = set_private_key_from_key_file(key_file_name, ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
och = (ptls_on_client_hello_t*)malloc(sizeof(ptls_on_client_hello_t) + sizeof(picoquic_quic_t*));
|
|
if (och != NULL) {
|
|
picoquic_quic_t** ppquic = (picoquic_quic_t**)(((char*)och) + sizeof(ptls_on_client_hello_t));
|
|
|
|
och->cb = picoquic_client_hello_call_back;
|
|
ctx->on_client_hello = och;
|
|
*ppquic = quic;
|
|
} else {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_server_setup_ticket_aead_contexts(quic, ctx, ticket_key, ticket_key_length);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
encrypt_ticket = (ptls_encrypt_ticket_t*)malloc(sizeof(ptls_encrypt_ticket_t) + sizeof(picoquic_quic_t*));
|
|
if (encrypt_ticket == NULL) {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
} else {
|
|
picoquic_quic_t** ppquic = (picoquic_quic_t**)(((char*)encrypt_ticket) + sizeof(ptls_encrypt_ticket_t));
|
|
|
|
encrypt_ticket->cb = picoquic_server_encrypt_ticket_call_back;
|
|
*ppquic = quic;
|
|
|
|
ctx->encrypt_ticket = encrypt_ticket;
|
|
ctx->ticket_lifetime = 100000; /* 100,000 seconds, a bit more than one day */
|
|
ctx->require_dhe_on_psk = 1;
|
|
ctx->max_early_data_size = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
ctx->verify_certificate = picoquic_get_certificate_verifier(cert_root_file_name, &is_cert_store_not_empty);
|
|
quic->is_cert_store_not_empty = is_cert_store_not_empty;
|
|
|
|
if (quic->ticket_file_name != NULL) {
|
|
save_ticket = (ptls_save_ticket_t*)malloc(sizeof(ptls_save_ticket_t) + sizeof(picoquic_quic_t*));
|
|
if (save_ticket != NULL) {
|
|
picoquic_quic_t** ppquic = (picoquic_quic_t**)(((char*)save_ticket) + sizeof(ptls_save_ticket_t));
|
|
|
|
save_ticket->cb = picoquic_client_save_ticket_call_back;
|
|
ctx->save_ticket = save_ticket;
|
|
*ppquic = quic;
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
/* Tell Picotls to not require EOED messages during handshake */
|
|
ctx->omit_end_of_early_data = 1;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
quic->tls_master_ctx = ctx;
|
|
picoquic_public_random_seed(quic);
|
|
} else {
|
|
free(ctx);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_certificates_list(ptls_iovec_t* certs, size_t len) {
|
|
if (certs == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < len; ++i) {
|
|
free(certs[i].base);
|
|
}
|
|
free(certs);
|
|
}
|
|
|
|
|
|
int picoquic_set_key_exchange(picoquic_quic_t* quic, int key_exchange_id)
|
|
{
|
|
int ret = 0;
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
ret = picoquic_set_key_exchange_in_ctx(ctx, key_exchange_id);
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_master_tlscontext_free(picoquic_quic_t* quic)
|
|
{
|
|
if (quic->tls_master_ctx != NULL) {
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
if (quic->p_simulated_time != NULL && ctx->get_time != NULL) {
|
|
free(ctx->get_time);
|
|
ctx->get_time = NULL;
|
|
}
|
|
|
|
free_certificates_list(ctx->certificates.list, ctx->certificates.count);
|
|
|
|
if (ctx->sign_certificate != NULL) {
|
|
picoquic_dispose_sign_certificate(ctx->sign_certificate);
|
|
free(ctx->sign_certificate);
|
|
ctx->sign_certificate = NULL;
|
|
}
|
|
|
|
picoquic_dispose_verify_certificate_callback(quic, 0);
|
|
|
|
if (ctx->on_client_hello != NULL) {
|
|
free(ctx->on_client_hello);
|
|
}
|
|
|
|
if (ctx->encrypt_ticket != NULL) {
|
|
free(ctx->encrypt_ticket);
|
|
}
|
|
|
|
if (ctx->update_traffic_key != NULL) {
|
|
free(ctx->update_traffic_key);
|
|
}
|
|
|
|
/* Need to be tested */
|
|
if (ctx->save_ticket != NULL) {
|
|
free(ctx->save_ticket);
|
|
}
|
|
|
|
if (ctx->cipher_suites != NULL) {
|
|
free((void*)ctx->cipher_suites);
|
|
}
|
|
|
|
picoquic_free_log_event(quic);
|
|
}
|
|
}
|
|
|
|
/* Return the virtual time seen by tls */
|
|
uint64_t picoquic_get_tls_time(picoquic_quic_t* quic)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
uint64_t now = ctx->get_time->cb(ctx->get_time);
|
|
|
|
return now;
|
|
}
|
|
|
|
/*
|
|
* Creation of a TLS context.
|
|
* This includes setting the handshake properties that will later be
|
|
* used during the TLS handshake.
|
|
*/
|
|
int picoquic_tlscontext_create(picoquic_quic_t* quic, picoquic_cnx_t* cnx, uint64_t current_time)
|
|
{
|
|
int ret = 0;
|
|
/* allocate a context structure */
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)malloc(sizeof(picoquic_tls_ctx_t));
|
|
|
|
/* Create the TLS context */
|
|
if (ctx == NULL) {
|
|
ret = -1;
|
|
} else {
|
|
memset(ctx, 0, sizeof(picoquic_tls_ctx_t));
|
|
|
|
ctx->cnx = cnx;
|
|
|
|
ctx->handshake_properties.collect_extension = picoquic_tls_collect_extensions_cb;
|
|
ctx->handshake_properties.collected_extensions = picoquic_tls_collected_extensions_cb;
|
|
ctx->client_mode = cnx->client_mode;
|
|
|
|
ctx->tls = ptls_new((ptls_context_t*)quic->tls_master_ctx,
|
|
(ctx->client_mode) ? 0 : 1);
|
|
*ptls_get_data_ptr(ctx->tls) = cnx;
|
|
|
|
if (ctx->tls == NULL) {
|
|
free(ctx);
|
|
ctx = NULL;
|
|
ret = -1;
|
|
} else if (!ctx->client_mode) {
|
|
/* A server side connection, but no cert/key where given for the master context */
|
|
if (((ptls_context_t*)quic->tls_master_ctx)->encrypt_ticket == NULL) {
|
|
ret = PICOQUIC_ERROR_TLS_SERVER_CON_WITHOUT_CERT;
|
|
picoquic_tlscontext_free(ctx);
|
|
ctx = NULL;
|
|
}
|
|
|
|
if (ctx != NULL) {
|
|
/* The server should never attempt a stateless retry */
|
|
ctx->handshake_properties.server.enforce_retry = 0;
|
|
ctx->handshake_properties.server.retry_uses_cookie = 0;
|
|
ctx->handshake_properties.server.cookie.key = NULL;
|
|
ctx->handshake_properties.server.cookie.additional_data.base = NULL;
|
|
ctx->handshake_properties.server.cookie.additional_data.len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cnx->tls_ctx != NULL) {
|
|
picoquic_tlscontext_free(cnx->tls_ctx);
|
|
}
|
|
|
|
cnx->tls_ctx = (void*)ctx;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Set the log event to record keys for use by Wireshark.
|
|
*/
|
|
|
|
static void picoquic_log_event_call_back(ptls_log_event_t *_self, ptls_t *tls, const char *type, const char *fmt, ...)
|
|
{
|
|
struct st_picoquic_log_event_t *self = (struct st_picoquic_log_event_t*)_self;
|
|
char randomhex[PTLS_HELLO_RANDOM_SIZE * 2 + 1];
|
|
va_list args;
|
|
|
|
if (self->fp != NULL) {
|
|
ptls_hexdump(randomhex, ptls_get_client_random(tls).base, PTLS_HELLO_RANDOM_SIZE);
|
|
fprintf(self->fp, "%s %s ", type, randomhex);
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(self->fp, fmt, args);
|
|
va_end(args);
|
|
|
|
fprintf(self->fp, "\n");
|
|
fflush(self->fp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Free the log-event call back, either when the TLS master context is freed,
|
|
* or when the key log file is reset.
|
|
*/
|
|
static void picoquic_free_log_event(picoquic_quic_t* quic)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
if (ctx->log_event != NULL) {
|
|
struct st_picoquic_log_event_t* picoquic_log_event = (struct st_picoquic_log_event_t*)ctx->log_event;
|
|
if (picoquic_log_event != NULL && picoquic_log_event->fp != NULL) {
|
|
picoquic_file_close(picoquic_log_event->fp);
|
|
}
|
|
free(ctx->log_event);
|
|
ctx->log_event = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the output file handle for writing traffic secrets in a format that can
|
|
* be recognized by Wireshark.
|
|
*/
|
|
void picoquic_set_key_log_file(picoquic_quic_t *quic, char const * keylog_filename)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
struct st_picoquic_log_event_t* log_event = (struct st_picoquic_log_event_t*)ctx->log_event;
|
|
|
|
if (log_event == NULL) {
|
|
log_event = (struct st_picoquic_log_event_t*)malloc(sizeof(struct st_picoquic_log_event_t));
|
|
if (log_event != NULL) {
|
|
log_event->super.cb = picoquic_log_event_call_back;
|
|
}
|
|
}
|
|
else {
|
|
if (log_event->fp != NULL) {
|
|
picoquic_file_close(log_event->fp);
|
|
log_event->fp = NULL;
|
|
}
|
|
}
|
|
|
|
if (log_event != NULL) {
|
|
log_event->fp = picoquic_file_open(keylog_filename, "a");
|
|
log_event->super.cb = picoquic_log_event_call_back;
|
|
ctx->log_event = (ptls_log_event_t*)log_event;
|
|
}
|
|
|
|
ctx->log_event = (ptls_log_event_t*)log_event;
|
|
}
|
|
|
|
/*
|
|
Check whether the ticket that was received, or used, authorizes 0-RTT data.
|
|
|
|
From TLS 1.3 spec:
|
|
struct {
|
|
uint32 ticket_lifetime;
|
|
uint32 ticket_age_add;
|
|
opaque ticket_nonce<0..255>;
|
|
opaque ticket<1..2^16-1>;
|
|
Extension extensions<0..2^16-2>;
|
|
} NewSessionTicket;
|
|
|
|
struct {
|
|
ExtensionType extension_type;
|
|
opaque extension_data<0..2^16-1>;
|
|
} Extension;
|
|
*/
|
|
|
|
int picoquic_does_tls_ticket_allow_early_data(uint8_t* ticket, uint16_t ticket_length)
|
|
{
|
|
uint8_t nonce_length = 0;
|
|
uint16_t ticket_val_length = 0;
|
|
uint16_t extension_length = 0;
|
|
uint8_t* extension_ptr = NULL;
|
|
uint16_t byte_index = 0;
|
|
uint16_t min_length = 4 + 4 + 1 + 2 + 2;
|
|
int ret = 0;
|
|
|
|
if (ticket_length >= min_length) {
|
|
byte_index += 4; /* Skip lifetime */
|
|
byte_index += 4; /* Skip age add */
|
|
nonce_length = ticket[byte_index++];
|
|
min_length += nonce_length;
|
|
if (ticket_length >= min_length) {
|
|
byte_index += nonce_length;
|
|
|
|
ticket_val_length = PICOPARSE_16(ticket + byte_index);
|
|
byte_index += 2;
|
|
min_length += ticket_val_length;
|
|
if (ticket_length >= min_length) {
|
|
byte_index += ticket_val_length;
|
|
|
|
extension_length = PICOPARSE_16(ticket + byte_index);
|
|
byte_index += 2;
|
|
min_length += extension_length;
|
|
if (ticket_length >= min_length) {
|
|
extension_ptr = &ticket[byte_index];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (extension_ptr != NULL) {
|
|
uint16_t x_index = 0;
|
|
|
|
while (x_index + 4 < extension_length) {
|
|
uint16_t x_type = PICOPARSE_16(extension_ptr + x_index);
|
|
uint16_t x_len = PICOPARSE_16(extension_ptr + x_index + 2);
|
|
x_index += 4 + x_len;
|
|
|
|
if (x_type == 42 && x_len == 4) {
|
|
uint32_t ed_len = PICOPARSE_32(extension_ptr + x_index - 4);
|
|
if (ed_len == 0xFFFFFFFF) {
|
|
ret = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Creation of a TLS context.
|
|
* This includes setting the handshake properties that will later be
|
|
* used during the TLS handshake.
|
|
*/
|
|
void picoquic_tlscontext_remove_ticket(picoquic_cnx_t* cnx)
|
|
{
|
|
/* allocate a context structure */
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)(cnx->tls_ctx);
|
|
|
|
ctx->handshake_properties.client.session_ticket.base = NULL;
|
|
ctx->handshake_properties.client.session_ticket.len = 0;
|
|
}
|
|
|
|
void picoquic_tlscontext_free(void* vctx)
|
|
{
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)vctx;
|
|
if (ctx->tls != NULL) {
|
|
ptls_free((ptls_t*)ctx->tls);
|
|
ctx->tls = NULL;
|
|
}
|
|
free(ctx);
|
|
}
|
|
|
|
char const* picoquic_tls_get_negotiated_alpn(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
|
|
return ptls_get_negotiated_protocol(ctx->tls);
|
|
}
|
|
|
|
char const* picoquic_tls_get_sni(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
|
|
return ptls_get_server_name(ctx->tls);
|
|
}
|
|
|
|
int picoquic_tls_is_psk_handshake(picoquic_cnx_t* cnx)
|
|
{
|
|
/* int ret = cnx->is_psk_handshake; */
|
|
int ret = ptls_is_psk_handshake(((picoquic_tls_ctx_t*)(cnx->tls_ctx))->tls);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Sending data on the crypto stream.
|
|
*/
|
|
|
|
static int picoquic_add_to_tls_stream(picoquic_cnx_t* cnx, const uint8_t* data, size_t length, int epoch)
|
|
{
|
|
int ret = 0;
|
|
picoquic_stream_head_t* stream = &cnx->tls_stream[epoch];
|
|
|
|
if (length > 0) {
|
|
picoquic_stream_data_node_t* stream_data = (picoquic_stream_data_node_t*)malloc(sizeof(picoquic_stream_data_node_t));
|
|
|
|
if (stream_data == 0) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
stream_data->bytes = (uint8_t*)malloc(length);
|
|
|
|
if (stream_data->bytes == NULL) {
|
|
free(stream_data);
|
|
stream_data = NULL;
|
|
ret = -1;
|
|
}
|
|
else {
|
|
picoquic_stream_data_node_t** pprevious = &stream->send_queue;
|
|
picoquic_stream_data_node_t* next = stream->send_queue;
|
|
|
|
memcpy(stream_data->bytes, data, length);
|
|
stream_data->length = length;
|
|
stream_data->offset = 0;
|
|
stream_data->next_stream_data = NULL;
|
|
|
|
while (next != NULL) {
|
|
pprevious = &next->next_stream_data;
|
|
next = next->next_stream_data;
|
|
}
|
|
|
|
*pprevious = stream_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Add a supported ALPN context */
|
|
int picoquic_add_proposed_alpn(void* tls_context, const char* alpn)
|
|
{
|
|
int ret = 0;
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)tls_context;
|
|
if (ctx == NULL) {
|
|
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
|
|
}
|
|
else if (ctx->alpn_count >= PICOQUIC_ALPN_NUMBER_MAX) {
|
|
ret = PICOQUIC_ERROR_SEND_BUFFER_TOO_SMALL;
|
|
} else {
|
|
ctx->alpn_vec[ctx->alpn_count].base = (uint8_t*)alpn;
|
|
ctx->alpn_vec[ctx->alpn_count].len = strlen(alpn);
|
|
ctx->alpn_count++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Prepare the initial message when starting a connection.
|
|
*/
|
|
|
|
int picoquic_initialize_tls_stream(picoquic_cnx_t* cnx, uint64_t current_time)
|
|
{
|
|
int ret = 0;
|
|
struct st_ptls_buffer_t sendbuf;
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
size_t epoch_offsets[PICOQUIC_NUMBER_OF_EPOCH_OFFSETS] = { 0, 0, 0, 0, 0 };
|
|
|
|
if (cnx->sni != NULL) {
|
|
ptls_set_server_name(ctx->tls, cnx->sni, strlen(cnx->sni));
|
|
}
|
|
|
|
if (cnx->alpn != NULL) {
|
|
ctx->alpn_vec[0].base = (uint8_t*)cnx->alpn;
|
|
ctx->alpn_vec[0].len = strlen(cnx->alpn);
|
|
ctx->handshake_properties.client.negotiated_protocols.count = 1;
|
|
ctx->handshake_properties.client.negotiated_protocols.list = ctx->alpn_vec;
|
|
}
|
|
else if (cnx->callback_fn != NULL) {
|
|
/* Get the default ALPN list for the callback function */
|
|
ret = cnx->callback_fn(cnx, 0, (uint8_t*)ctx, 0, picoquic_callback_request_alpn_list, cnx->callback_ctx, NULL);
|
|
|
|
ctx->handshake_properties.client.negotiated_protocols.count = ctx->alpn_count;
|
|
ctx->handshake_properties.client.negotiated_protocols.list = ctx->alpn_vec;
|
|
|
|
if (ret != 0) {
|
|
DBG_PRINTF("ALPN list callback returns 0x%x", ret);
|
|
}
|
|
}
|
|
|
|
/* ALPN is mandatory, there should be at least one */
|
|
if (ret == 0 && ctx->handshake_properties.client.negotiated_protocols.count == 0) {
|
|
ret = PICOQUIC_ERROR_NO_ALPN_PROVIDED;
|
|
DBG_PRINTF("No ALPN provided, error 0x%x", ret);
|
|
}
|
|
|
|
picoquic_log_negotiated_alpn(cnx,
|
|
1, (const uint8_t *)cnx->sni, (cnx->sni == NULL)?0:strlen(cnx->sni), NULL, 0,
|
|
ctx->handshake_properties.client.negotiated_protocols.list,
|
|
ctx->handshake_properties.client.negotiated_protocols.count);
|
|
|
|
/* No resumption if no alpn specified upfront, because it would make the negotiation and
|
|
* the handling of 0-RTT way too messy */
|
|
if (cnx->sni != NULL && cnx->alpn != NULL && !cnx->quic->client_zero_share) {
|
|
uint8_t* ticket = NULL;
|
|
uint16_t ticket_length = 0;
|
|
|
|
if (picoquic_get_ticket(cnx->quic->p_first_ticket, current_time,
|
|
cnx->sni, (uint16_t)strlen(cnx->sni), cnx->alpn, (uint16_t)strlen(cnx->alpn),
|
|
&ticket, &ticket_length, &cnx->remote_parameters, 1)
|
|
== 0) {
|
|
ctx->handshake_properties.client.session_ticket.base = ticket;
|
|
ctx->handshake_properties.client.session_ticket.len = ticket_length;
|
|
|
|
ctx->handshake_properties.client.max_early_data_size = &cnx->max_early_data_size;
|
|
|
|
cnx->psk_cipher_suite_id = PICOPARSE_16(ticket + 8);
|
|
}
|
|
}
|
|
|
|
if (cnx->quic->client_zero_share &&
|
|
cnx->cnx_state == picoquic_state_client_init)
|
|
{
|
|
ctx->handshake_properties.client.negotiate_before_key_exchange = 1;
|
|
}
|
|
else
|
|
{
|
|
ctx->handshake_properties.client.negotiate_before_key_exchange = 0;
|
|
}
|
|
|
|
if (ret != 0) {
|
|
DBG_PRINTF("Could not set up TLS parameters, error 0x%x, abandoning connection", ret);
|
|
cnx->cnx_state = picoquic_state_disconnected;
|
|
} else {
|
|
picoquic_tls_set_extensions(cnx, ctx);
|
|
|
|
ptls_buffer_init(&sendbuf, "", 0);
|
|
|
|
/* Clearing the global error state of the crypto provider before calling handle message.
|
|
* This allows detection of errors during processing. */
|
|
picoquic_clear_crypto_errors();
|
|
ret = ptls_handle_message(ctx->tls, &sendbuf, epoch_offsets, 0, NULL, 0, &ctx->handshake_properties);
|
|
|
|
/* assume that all the data goes to epoch 0, initial */
|
|
if ((ret == 0 || ret == PTLS_ERROR_IN_PROGRESS)) {
|
|
#ifdef PTLS_ESNI_NONCE_SIZE
|
|
/* Find the ESNI secret if any, and copy key values to picoquic tls context */
|
|
struct st_ptls_esni_secret_t* esni = ptls_get_esni_secret(ctx->tls);
|
|
if (esni != NULL) {
|
|
ctx->esni_version = esni->version;
|
|
memcpy(ctx->esni_nonce, esni->nonce, PTLS_ESNI_NONCE_SIZE);
|
|
}
|
|
#endif
|
|
if (sendbuf.off > 0) {
|
|
ret = picoquic_add_to_tls_stream(cnx, sendbuf.base, sendbuf.off, 0);
|
|
}
|
|
else {
|
|
ret = 0;
|
|
}
|
|
}
|
|
else {
|
|
picoquic_log_crypto_errors(cnx, ret);
|
|
ret = -1;
|
|
}
|
|
ptls_buffer_dispose(&sendbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Packet number encryption and decryption utilities
|
|
*/
|
|
|
|
void * picoquic_pn_enc_create_for_test(const uint8_t * secret)
|
|
{
|
|
ptls_cipher_suite_t *cipher = picoquic_get_aes128gcm_sha256();
|
|
void *v_pn_enc = NULL;
|
|
|
|
(void)picoquic_set_pn_enc_from_secret(&v_pn_enc, cipher, 1, secret);
|
|
|
|
return v_pn_enc;
|
|
}
|
|
|
|
size_t picoquic_pn_iv_size(void *pn_enc)
|
|
{
|
|
return ((ptls_cipher_context_t *)pn_enc)->algo->iv_size;
|
|
}
|
|
|
|
void picoquic_pn_encrypt(void *pn_enc, const void * iv, void *output, const void *input, size_t len)
|
|
{
|
|
ptls_cipher_init((ptls_cipher_context_t *) pn_enc, iv);
|
|
ptls_cipher_encrypt((ptls_cipher_context_t *) pn_enc, output, input, len);
|
|
}
|
|
|
|
/* Utility functions, so applications do not have to load picotls.h */
|
|
|
|
void picoquic_aead_free(void* aead_context)
|
|
{
|
|
ptls_aead_free((ptls_aead_context_t*)aead_context);
|
|
}
|
|
|
|
size_t picoquic_aead_get_checksum_length(void* aead_context)
|
|
{
|
|
size_t tag_size = ((ptls_aead_context_t*)aead_context)->algo->tag_size;
|
|
/* TODO: remove this temporary fix to deal with Feb 2019 change in picotls */
|
|
if (tag_size > 16) {
|
|
tag_size = 16;
|
|
}
|
|
return tag_size;
|
|
}
|
|
|
|
/* Setting of encryption contexts for test */
|
|
void * picoquic_setup_test_aead_context(int is_encrypt, const uint8_t * secret)
|
|
{
|
|
void * v_aead = NULL;
|
|
ptls_cipher_suite_t* cipher = picoquic_get_aes128gcm_sha256();
|
|
|
|
(void)picoquic_set_aead_from_secret(&v_aead, cipher, is_encrypt, secret);
|
|
|
|
return v_aead;
|
|
}
|
|
|
|
int picoquic_server_setup_ticket_aead_contexts(picoquic_quic_t* quic,
|
|
ptls_context_t* tls_ctx,
|
|
const uint8_t* secret, size_t secret_length)
|
|
{
|
|
int ret = 0;
|
|
uint8_t temp_secret[256]; /* secret_max */
|
|
ptls_cipher_suite_t *cipher = picoquic_get_aes128gcm_sha256();
|
|
|
|
if (cipher->hash->digest_size > sizeof(temp_secret)) {
|
|
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
|
|
} else {
|
|
if (secret != NULL && secret_length > 0) {
|
|
memset(temp_secret, 0, cipher->hash->digest_size);
|
|
memcpy(temp_secret, secret, (secret_length > cipher->hash->digest_size) ? cipher->hash->digest_size : secret_length);
|
|
} else {
|
|
tls_ctx->random_bytes(temp_secret, cipher->hash->digest_size);
|
|
}
|
|
|
|
/* Create the AEAD contexts */
|
|
ret = picoquic_set_aead_from_secret(&quic->aead_encrypt_ticket_ctx, cipher, 1, temp_secret);
|
|
if (ret == 0) {
|
|
ret = picoquic_set_aead_from_secret(&quic->aead_decrypt_ticket_ctx, cipher, 0, temp_secret);
|
|
}
|
|
|
|
/* erase the temporary secret */
|
|
ptls_clear_memory(temp_secret, cipher->hash->digest_size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Access integrity limit for AEAD */
|
|
uint64_t picoquic_aead_integrity_limit(void* aead_ctx)
|
|
{
|
|
return ((ptls_aead_context_t*)aead_ctx)->algo->integrity_limit;
|
|
}
|
|
|
|
/* Access confidentiality limit for AEAD */
|
|
uint64_t picoquic_aead_confidentiality_limit(void* aead_ctx)
|
|
{
|
|
return ((ptls_aead_context_t*)aead_ctx)->algo->confidentiality_limit;
|
|
}
|
|
|
|
/* AEAD encrypt/decrypt routines */
|
|
size_t picoquic_aead_decrypt_generic(uint8_t* output, const uint8_t* input, size_t input_length,
|
|
uint64_t seq_num, const uint8_t* auth_data, size_t auth_data_length, void* aead_ctx)
|
|
{
|
|
size_t decrypted = 0;
|
|
|
|
if (aead_ctx == NULL) {
|
|
decrypted = SIZE_MAX;
|
|
} else {
|
|
decrypted = ptls_aead_decrypt((ptls_aead_context_t*)aead_ctx,
|
|
(void*)output, (const void*)input, input_length, seq_num,
|
|
(void*)auth_data, auth_data_length);
|
|
}
|
|
|
|
return decrypted;
|
|
}
|
|
|
|
size_t picoquic_aead_encrypt_generic(uint8_t* output, const uint8_t* input, size_t input_length,
|
|
uint64_t seq_num, const uint8_t* auth_data, size_t auth_data_length, void* aead_context)
|
|
{
|
|
size_t encrypted = 0;
|
|
|
|
encrypted = ptls_aead_encrypt((ptls_aead_context_t*)aead_context,
|
|
(void*)output, (const void*)input, input_length, seq_num,
|
|
(void*)auth_data, auth_data_length);
|
|
|
|
return encrypted;
|
|
}
|
|
|
|
/* management of version specific salt, for initial packet encryption.
|
|
*/
|
|
|
|
uint8_t picoquic_cleartext_null_salt[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0
|
|
};
|
|
|
|
static void picoquic_setup_cleartext_aead_salt(size_t version_index, ptls_iovec_t* salt)
|
|
{
|
|
if (picoquic_supported_versions[version_index].version_aead_key != NULL && picoquic_supported_versions[version_index].version_aead_key_length > 0) {
|
|
salt->base = picoquic_supported_versions[version_index].version_aead_key;
|
|
salt->len = picoquic_supported_versions[version_index].version_aead_key_length;
|
|
} else {
|
|
salt->base = picoquic_cleartext_null_salt;
|
|
salt->len = sizeof(picoquic_cleartext_null_salt);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* TODO: find a replacement for this test. */
|
|
/* Compare AEAD context parameters. This is done just by comparing the IV,
|
|
* which is accessible in the context */
|
|
int picoquic_compare_cleartext_aead_contexts(picoquic_cnx_t* cnx1, picoquic_cnx_t* cnx2)
|
|
{
|
|
int ret = 0;
|
|
ptls_aead_context_t * aead_enc = (ptls_aead_context_t *)cnx1->crypto_context[0].aead_encrypt;
|
|
ptls_aead_context_t * aead_dec = (ptls_aead_context_t *)cnx2->crypto_context[0].aead_decrypt;
|
|
|
|
if (aead_enc == NULL )
|
|
{
|
|
DBG_PRINTF("%s", "Missing aead encoding context\n");
|
|
ret = -1;
|
|
}
|
|
else if (aead_dec == NULL)
|
|
{
|
|
DBG_PRINTF("%s", "Missing aead decoding context\n");
|
|
ret = -1;
|
|
}
|
|
else if (memcmp(aead_enc->static_iv, aead_dec->static_iv, 16) != 0){
|
|
DBG_PRINTF("%s", "Encoding IV does not match decoding IV\n");
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Input stream zero data to TLS context.
|
|
*
|
|
* Processing depends on the "epoch" in which packets have been received. That
|
|
* epoch is be passed through the ptls_handle_message() API.
|
|
* The API has an "epoch offset" parameter that documents how many bytes of the
|
|
* should be sent at each epoch.
|
|
*/
|
|
|
|
int picoquic_tls_stream_process(picoquic_cnx_t* cnx, int * data_consumed)
|
|
{
|
|
int ret = 0;
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
size_t next_epoch = 0;
|
|
|
|
/* Provide indication of current connection for later callbacks */
|
|
cnx->quic->cnx_in_progress = cnx;
|
|
|
|
for (size_t epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS && ret == 0; epoch++) {
|
|
picoquic_stream_head_t* stream = &cnx->tls_stream[epoch];
|
|
picoquic_stream_data_node_t* data = (picoquic_stream_data_node_t*)picosplay_first(&stream->stream_data_tree);
|
|
size_t processed = 0;
|
|
int data_pushed = 0;
|
|
|
|
next_epoch = ptls_get_read_epoch(ctx->tls);
|
|
|
|
if (epoch != next_epoch) {
|
|
if (epoch > next_epoch) {
|
|
break;
|
|
} else {
|
|
if (data != NULL && data->offset > stream->consumed_offset) {
|
|
/* Protocol error: data received that could not be read */
|
|
#ifdef _DEBUG
|
|
DBG_PRINTF("Connection error - TLS data at epoch %d, expected %d.\n",
|
|
epoch, next_epoch);
|
|
#endif
|
|
ret = picoquic_connection_error(cnx,
|
|
PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
while ((ret == 0 || ret == PTLS_ERROR_IN_PROGRESS) &&
|
|
data != NULL && data->offset <= stream->consumed_offset) {
|
|
struct st_ptls_buffer_t sendbuf;
|
|
size_t start = (size_t)(stream->consumed_offset - data->offset);
|
|
size_t epoch_data = data->length - start;
|
|
size_t send_offset[PICOQUIC_NUMBER_OF_EPOCH_OFFSETS] = { 0, 0, 0, 0, 0 };
|
|
|
|
if (data_consumed != NULL) {
|
|
*data_consumed = 1;
|
|
}
|
|
|
|
ptls_buffer_init(&sendbuf, "", 0);
|
|
|
|
/* Clearing the global error state of the crypto provider before calling handle message.
|
|
* This allows detection of errors during processing. */
|
|
picoquic_clear_crypto_errors();
|
|
|
|
ret = ptls_handle_message(ctx->tls, &sendbuf, send_offset, epoch,
|
|
data->bytes + start, epoch_data, &ctx->handshake_properties);
|
|
|
|
if ((ret == 0 || ret == PTLS_ERROR_IN_PROGRESS ||
|
|
ret == PTLS_ERROR_STATELESS_RETRY)) {
|
|
for (int i = 0; i < PICOQUIC_NUMBER_OF_EPOCHS; i++) {
|
|
if (send_offset[i] < send_offset[i + 1]) {
|
|
data_pushed = 1;
|
|
ret = picoquic_add_to_tls_stream(cnx,
|
|
sendbuf.base + send_offset[i], send_offset[i + 1] - send_offset[i], i);
|
|
}
|
|
}
|
|
if (cnx->client_mode) {
|
|
if (cnx->alpn == NULL) {
|
|
const char* alpn = ptls_get_negotiated_protocol(ctx->tls);
|
|
|
|
if (alpn != NULL){
|
|
cnx->alpn = picoquic_string_duplicate(alpn);
|
|
|
|
picoquic_log_negotiated_alpn(cnx, 0, NULL, 0, (const uint8_t*)alpn, strlen(alpn), NULL, 0);
|
|
|
|
if (cnx->callback_fn != NULL) {
|
|
cnx->callback_fn(cnx, 0, (uint8_t*)alpn, 0, picoquic_callback_set_alpn, cnx->callback_ctx, NULL);
|
|
}
|
|
else {
|
|
DBG_PRINTF("Negotiated ALPN: %s", alpn);
|
|
}
|
|
}
|
|
}
|
|
switch (ctx->handshake_properties.client.early_data_acceptance) {
|
|
case PTLS_EARLY_DATA_REJECTED:
|
|
cnx->zero_rtt_data_accepted = 0;
|
|
break;
|
|
case PTLS_EARLY_DATA_ACCEPTED:
|
|
cnx->zero_rtt_data_accepted = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
picoquic_log_crypto_errors(cnx, ret);
|
|
}
|
|
|
|
stream->consumed_offset += epoch_data;
|
|
processed += epoch_data;
|
|
|
|
if (start + epoch_data >= data->length) {
|
|
picosplay_delete_hint(&cnx->tls_stream[epoch].stream_data_tree, &data->stream_data_node);
|
|
data = (picoquic_stream_data_node_t*)picosplay_first(&cnx->tls_stream[epoch].stream_data_tree);
|
|
}
|
|
|
|
ptls_buffer_dispose(&sendbuf);
|
|
}
|
|
|
|
if (processed > 0) {
|
|
if (ret == 0) {
|
|
switch (cnx->cnx_state) {
|
|
case picoquic_state_client_retry_received:
|
|
/* This is not supposed to happen -- HRR should generate "error in progress" */
|
|
break;
|
|
case picoquic_state_client_init:
|
|
case picoquic_state_client_init_sent:
|
|
case picoquic_state_client_renegotiate:
|
|
case picoquic_state_client_init_resent:
|
|
case picoquic_state_client_handshake_start:
|
|
if (ptls_handshake_is_complete(ctx->tls)) {
|
|
if (cnx->remote_parameters_received == 0) {
|
|
|
|
#ifdef _DEBUG
|
|
DBG_PRINTF("%s", "Connection error - no transport parameter received.\n");
|
|
#endif
|
|
ret = picoquic_connection_error(cnx,
|
|
PICOQUIC_TRANSPORT_PARAMETER_ERROR, 0);
|
|
}
|
|
else {
|
|
if (cnx->crypto_context[3].aead_encrypt != NULL) {
|
|
cnx->cnx_state = picoquic_state_client_almost_ready;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case picoquic_state_server_init:
|
|
case picoquic_state_server_handshake:
|
|
/* If client authentication is activated, the client sends the certificates with its `Finished` packet.
|
|
The server does not send any further packets, so, we can switch into false start state here.
|
|
*/
|
|
if (data_pushed == 0 && ((ptls_context_t*)cnx->quic->tls_master_ctx)->require_client_authentication == 1) {
|
|
cnx->cnx_state = picoquic_state_server_false_start;
|
|
|
|
/* On a server that does address validation, send a NEW TOKEN frame */
|
|
if (!cnx->client_mode && (cnx->quic->check_token||cnx->quic->provide_token)) {
|
|
uint8_t token_buffer[256];
|
|
size_t token_size;
|
|
picoquic_connection_id_t n_cid = picoquic_null_connection_id;
|
|
|
|
if (picoquic_prepare_retry_token(cnx->quic, (struct sockaddr *)&cnx->path[0]->peer_addr,
|
|
picoquic_get_quic_time(cnx->quic) + PICOQUIC_TOKEN_DELAY_LONG, &n_cid, &n_cid, 0,
|
|
token_buffer, sizeof(token_buffer), &token_size) == 0) {
|
|
if (picoquic_queue_new_token_frame(cnx, token_buffer, token_size) != 0) {
|
|
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_INTERNAL_ERROR, picoquic_frame_type_new_token);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (cnx->crypto_context[3].aead_encrypt != NULL) {
|
|
cnx->cnx_state = picoquic_state_server_almost_ready;
|
|
}
|
|
}
|
|
break;
|
|
case picoquic_state_client_almost_ready:
|
|
case picoquic_state_handshake_failure:
|
|
case picoquic_state_handshake_failure_resend:
|
|
case picoquic_state_client_ready_start:
|
|
case picoquic_state_server_almost_ready:
|
|
case picoquic_state_server_false_start:
|
|
case picoquic_state_ready:
|
|
case picoquic_state_disconnecting:
|
|
case picoquic_state_closing_received:
|
|
case picoquic_state_closing:
|
|
case picoquic_state_draining:
|
|
case picoquic_state_disconnected:
|
|
break;
|
|
default:
|
|
DBG_PRINTF("Unexpected connection state: %d\n", cnx->cnx_state);
|
|
break;
|
|
}
|
|
}
|
|
else if (ret == PTLS_ERROR_IN_PROGRESS && (cnx->cnx_state == picoquic_state_client_init || cnx->cnx_state == picoquic_state_client_init_sent || cnx->cnx_state == picoquic_state_client_init_resent)) {
|
|
/* Extract and install the client 0-RTT key */
|
|
#ifdef _DEBUG
|
|
DBG_PRINTF("%s", "Handshake not yet complete.\n");
|
|
#endif
|
|
}
|
|
else if (ret == PTLS_ERROR_IN_PROGRESS &&
|
|
(cnx->cnx_state == picoquic_state_server_init ||
|
|
cnx->cnx_state == picoquic_state_server_handshake))
|
|
{
|
|
if (ptls_handshake_is_complete(ctx->tls))
|
|
{
|
|
cnx->cnx_state = picoquic_state_server_almost_ready;
|
|
}
|
|
}
|
|
|
|
if ((ret == 0 || ret == PTLS_ERROR_IN_PROGRESS || ret == PTLS_ERROR_STATELESS_RETRY)) {
|
|
ret = 0;
|
|
}
|
|
else {
|
|
uint16_t error_code = PICOQUIC_TRANSPORT_INTERNAL_ERROR;
|
|
|
|
if (PTLS_ERROR_GET_CLASS(ret) == PTLS_ERROR_CLASS_SELF_ALERT) {
|
|
error_code = PICOQUIC_TRANSPORT_CRYPTO_ERROR(ret);
|
|
}
|
|
#ifdef _DEBUG
|
|
DBG_PRINTF("Handshake failed, ret = 0x%x.\n", ret);
|
|
#endif
|
|
(void)picoquic_connection_error(cnx, error_code, 0);
|
|
ret = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Reset indication of current connection */
|
|
cnx->quic->cnx_in_progress = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Test whether the TLS handshake is complete according to TLS stack
|
|
*/
|
|
int picoquic_is_tls_complete(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
return ptls_handshake_is_complete(ctx->tls);
|
|
}
|
|
|
|
/*
|
|
* Compute the 16 byte reset secret associated with a connection ID.
|
|
* We implement it as the hash of a secret seed maintained per QUIC context
|
|
* and the 8 bytes connection ID.
|
|
* This is written portable hash APIs.
|
|
*/
|
|
|
|
int picoquic_create_cnxid_reset_secret(picoquic_quic_t* quic, picoquic_connection_id_t * cnx_id,
|
|
uint8_t reset_secret[PICOQUIC_RESET_SECRET_SIZE])
|
|
{
|
|
int ret = 0;
|
|
ptls_hash_algorithm_t* algo = picoquic_get_sha256();
|
|
|
|
if (algo == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ptls_hash_context_t* hash_ctx = algo->create();
|
|
uint8_t final_hash[PTLS_MAX_DIGEST_SIZE];
|
|
|
|
if (hash_ctx == NULL) {
|
|
ret = -1;
|
|
memset(reset_secret, 0, PICOQUIC_RESET_SECRET_SIZE);
|
|
}
|
|
else {
|
|
hash_ctx->update(hash_ctx, quic->reset_seed, sizeof(quic->reset_seed));
|
|
hash_ctx->update(hash_ctx, cnx_id, sizeof(picoquic_connection_id_t));
|
|
hash_ctx->final(hash_ctx, final_hash, PTLS_HASH_FINAL_MODE_FREE);
|
|
memcpy(reset_secret, final_hash, PICOQUIC_RESET_SECRET_SIZE);
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
void picoquic_set_tls_certificate_chain(picoquic_quic_t* quic, ptls_iovec_t* certs, size_t count)
|
|
{
|
|
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
|
|
|
|
free_certificates_list(ctx->certificates.list, ctx->certificates.count);
|
|
|
|
ctx->certificates.list = certs;
|
|
ctx->certificates.count = count;
|
|
}
|
|
|
|
void picoquic_tls_set_client_authentication(picoquic_quic_t* quic, int client_authentication) {
|
|
((ptls_context_t*)quic->tls_master_ctx)->require_client_authentication = client_authentication;
|
|
}
|
|
|
|
int picoquic_tls_client_authentication_activated(picoquic_quic_t* quic) {
|
|
return ((ptls_context_t*)quic->tls_master_ctx)->require_client_authentication;
|
|
}
|
|
|
|
/*
|
|
* Create or verify a token. Tokens are tied to an IP address and a time of
|
|
* issue, and come in two variations:
|
|
* - specific tokens are tied to an Original DCID.
|
|
* - generic tokens work with a zero length DCID.
|
|
* The structure of the token is:
|
|
* - time valid until: uint64_t
|
|
* - ODCID length, one byte
|
|
* - ODCID, length bytes
|
|
* This is encrypted using the same AEAD contexts as the encryption of session tickets.
|
|
* The encrypted structure is:
|
|
* - 64 bit random sequence number.
|
|
* - Encrypted value of the token.
|
|
* - AEAD checksum.
|
|
* The most significant bit of the random number is set to 1 (0x80) for a "new token",
|
|
* and to zero for a "retry token".
|
|
* When invoking AEAD, the sequence number is used to update the IV, and the IP address
|
|
* is passed as "authenticated" data. The 64 bit random number alleviates the concern of
|
|
* reusing the same AEAD key twice. The authenticated data ensures that if the token is
|
|
* used from a different address, the decryption will fail.
|
|
*/
|
|
|
|
static int picoquic_server_encrypt_retry_token(picoquic_quic_t * quic, const struct sockaddr * addr_peer,
|
|
int is_new_token,
|
|
uint8_t * token, size_t * token_length, size_t token_max, const uint8_t * text, size_t text_length)
|
|
{
|
|
int ret = 0;
|
|
uint64_t sequence;
|
|
uint8_t* auth_data;
|
|
size_t auth_data_length;
|
|
|
|
if (text_length + 1u + 16u > token_max) {
|
|
ret = -1;
|
|
*token_length = 0;
|
|
}
|
|
else {
|
|
|
|
if (addr_peer->sa_family == AF_INET) {
|
|
auth_data = (uint8_t*)&((struct sockaddr_in*)addr_peer)->sin_addr;
|
|
auth_data_length = 4;
|
|
}
|
|
else {
|
|
auth_data = (uint8_t*)&((struct sockaddr_in6*)addr_peer)->sin6_addr;
|
|
auth_data_length = 16;
|
|
}
|
|
picoquic_crypto_random(quic, token, 8);
|
|
if (is_new_token) {
|
|
token[0] |= 0x80;
|
|
}
|
|
else {
|
|
token[0] &= 0x7F;
|
|
}
|
|
sequence = PICOPARSE_64(token);
|
|
|
|
*token_length = (size_t)8u + picoquic_aead_encrypt_generic(token + 8, text, text_length,
|
|
sequence, auth_data, auth_data_length, quic->aead_encrypt_ticket_ctx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_server_decrypt_retry_token(picoquic_quic_t* quic, const struct sockaddr * addr_peer,
|
|
int * is_new_token, const uint8_t * token, size_t token_length, uint8_t * text, size_t *text_length)
|
|
{
|
|
int ret = 0;
|
|
uint64_t sequence;
|
|
uint8_t* auth_data;
|
|
size_t auth_data_length;
|
|
|
|
if (addr_peer->sa_family == AF_INET) {
|
|
auth_data = (uint8_t*)&((struct sockaddr_in *)addr_peer)->sin_addr;
|
|
auth_data_length = 4;
|
|
}
|
|
else {
|
|
auth_data = (uint8_t*)&((struct sockaddr_in6 *)addr_peer)->sin6_addr;
|
|
auth_data_length = 16;
|
|
}
|
|
|
|
if (token_length < 8) {
|
|
*is_new_token = 0;
|
|
ret = -1;
|
|
}
|
|
else {
|
|
*is_new_token = ((token[0] & 0x80) == 0) ? 0: 1;
|
|
sequence = PICOPARSE_64(token);
|
|
|
|
*text_length = picoquic_aead_decrypt_generic(text, token+8, token_length-8,
|
|
sequence, auth_data, auth_data_length, quic->aead_decrypt_ticket_ctx);
|
|
if (*text_length >= token_length - 8) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_prepare_retry_token(picoquic_quic_t* quic, const struct sockaddr* addr_peer,
|
|
uint64_t current_time, const picoquic_connection_id_t* odcid, const picoquic_connection_id_t* rcid,
|
|
uint32_t initial_pn,
|
|
uint8_t* token, size_t token_max, size_t* token_size)
|
|
{
|
|
int ret = 0;
|
|
uint8_t text[128];
|
|
uint64_t token_time = current_time;
|
|
uint8_t* bytes = text;
|
|
uint8_t* bytes_max = text + sizeof(text);
|
|
|
|
/* set a short life time for short lived tokens, 24 hours otherwise */
|
|
if (odcid->id_len == 0) {
|
|
token_time += 24ull * 3600ull * 1000000ull;
|
|
}
|
|
else {
|
|
token_time += 4000000ull;
|
|
}
|
|
/* serialize the token components */
|
|
if ((bytes = picoquic_frames_uint64_encode(bytes, bytes_max, token_time)) != NULL &&
|
|
(bytes = picoquic_frames_cid_encode(bytes, bytes_max, odcid)) != NULL &&
|
|
(bytes = picoquic_frames_cid_encode(bytes, bytes_max, rcid)) != NULL &&
|
|
(bytes = picoquic_frames_varint_encode(bytes, bytes_max, initial_pn)) != NULL) {
|
|
/* Pad to min token size */
|
|
while (bytes < text + PICOQUIC_RETRY_TOKEN_PAD_SIZE) {
|
|
*bytes++ = 0;
|
|
}
|
|
/* Encode the clear text components */
|
|
ret = picoquic_server_encrypt_retry_token(quic, addr_peer, odcid->id_len == 0,
|
|
token, token_size, token_max, text, bytes - text);
|
|
}
|
|
else {
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_verify_retry_token(picoquic_quic_t* quic, const struct sockaddr * addr_peer,
|
|
uint64_t current_time, int * is_new_token, picoquic_connection_id_t * odcid, const picoquic_connection_id_t* rcid,
|
|
uint32_t initial_pn,
|
|
const uint8_t * token, size_t token_size, int new_context_created)
|
|
{
|
|
int ret = 0;
|
|
uint8_t text[128];
|
|
size_t text_len = 0;
|
|
picoquic_connection_id_t cid;
|
|
uint64_t token_pn;
|
|
|
|
odcid->id_len = 0;
|
|
|
|
/* decode the encrypted token */
|
|
ret = picoquic_server_decrypt_retry_token(quic, addr_peer, is_new_token, token, token_size,
|
|
text, &text_len);
|
|
|
|
if (ret == 0) {
|
|
/* Decode the clear text components */
|
|
const uint8_t* bytes = text;
|
|
const uint8_t* bytes_max = text + text_len;
|
|
uint64_t token_time = PICOPARSE_64(text);
|
|
|
|
if ((bytes = picoquic_frames_uint64_decode(bytes, bytes_max, &token_time)) != NULL &&
|
|
(bytes = picoquic_frames_cid_decode(bytes, bytes_max, odcid)) != NULL &&
|
|
(bytes = picoquic_frames_cid_decode(bytes, bytes_max, &cid)) != NULL &&
|
|
(bytes = picoquic_frames_varint_decode(bytes, bytes_max, &token_pn)) != NULL) {
|
|
if (token_time < current_time) {
|
|
/* Invalid token, too old */
|
|
ret = -1;
|
|
}
|
|
else if (odcid->id_len > 0 && token_pn >= initial_pn) {
|
|
/* Invalid PN number */
|
|
ret = -1;
|
|
}
|
|
else {
|
|
/* Remove old tickets before testing this one. */
|
|
picoquic_registered_token_clear(quic, current_time);
|
|
if (new_context_created && (ret = picoquic_registered_token_check_reuse(quic, token, token_size, token_time)) != 0) {
|
|
picoquic_log_context_free_app_message(quic, rcid, "Duplicate token test returns %d", ret);
|
|
}
|
|
else if (odcid->id_len > 0 &&
|
|
picoquic_compare_connection_id(rcid, &cid) != 0) {
|
|
/* Invalid token, bad rcid */
|
|
ret = -1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
*odcid = picoquic_null_connection_id;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Encryption functions for CID encryption
|
|
*/
|
|
|
|
void picoquic_cid_free_under_mask_ctx(void * v_cid_enc)
|
|
{
|
|
if (v_cid_enc != NULL) {
|
|
ptls_cipher_free((ptls_cipher_context_t *)v_cid_enc);
|
|
}
|
|
}
|
|
|
|
int picoquic_cid_get_under_mask_ctx(void ** v_cid_enc, const void *secret)
|
|
{
|
|
uint8_t cidkey[PTLS_MAX_SECRET_SIZE];
|
|
uint8_t long_secret[PTLS_MAX_DIGEST_SIZE];
|
|
ptls_cipher_suite_t * cipher = picoquic_get_aes128gcm_sha256();
|
|
int ret;
|
|
|
|
picoquic_cid_free_under_mask_ctx(*v_cid_enc);
|
|
*v_cid_enc = NULL;
|
|
/* Secret is only guaranteed to be 16 bytes long. Avoid excess length issues */
|
|
memset(long_secret, 0, sizeof(long_secret));
|
|
memcpy(long_secret, secret, 16);
|
|
|
|
if ((ret = ptls_hkdf_expand_label(cipher->hash, cidkey,
|
|
cipher->aead->ctr_cipher->key_size, ptls_iovec_init(long_secret, cipher->hash->digest_size),
|
|
PICOQUIC_LABEL_CID, ptls_iovec_init(NULL, 0), PICOQUIC_LABEL_QUIC_KEY_BASE)) == 0) {
|
|
#ifdef _DEBUG
|
|
DBG_PRINTF("CID Encryption key (%d):\n", (int)cipher->aead->ctr_cipher->key_size);
|
|
debug_dump(cidkey, (int)cipher->aead->ctr_cipher->key_size);
|
|
#endif
|
|
if ((*v_cid_enc = ptls_cipher_new(cipher->aead->ctr_cipher, 1, cidkey)) == NULL) {
|
|
ret = PTLS_ERROR_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_cid_encrypt_under_mask(void *cid_enc, const picoquic_connection_id_t * cid_in, const picoquic_connection_id_t * mask,
|
|
picoquic_connection_id_t * cid_out)
|
|
{
|
|
uint8_t unmasked[18];
|
|
uint8_t val[18];
|
|
|
|
memset(unmasked, 0, 18);
|
|
memset(val, 0, 18);
|
|
|
|
for (uint8_t i = 0; i < cid_in->id_len; i++) {
|
|
/* retain only the random bits */
|
|
unmasked[i] = cid_in->id[i] & mask->id[i];
|
|
}
|
|
|
|
ptls_cipher_init((ptls_cipher_context_t *)cid_enc, unmasked);
|
|
ptls_cipher_encrypt((ptls_cipher_context_t *)cid_enc, val, val, cid_in->id_len);
|
|
|
|
for (uint8_t i = 0; i < cid_in->id_len; i++) {
|
|
/* randomize the unmasked bits */
|
|
cid_out->id[i] = cid_in->id[i]^(val[i] & ~mask->id[i]);
|
|
}
|
|
cid_out->id_len = cid_in->id_len;
|
|
if (cid_out->id_len < 18) {
|
|
memset(cid_out->id + cid_out->id_len, 0, 18 - cid_out->id_len);
|
|
}
|
|
}
|
|
|
|
void picoquic_cid_decrypt_under_mask(void *cid_enc, const picoquic_connection_id_t * cid_in, const picoquic_connection_id_t * mask,
|
|
picoquic_connection_id_t * cid_out)
|
|
{
|
|
picoquic_cid_encrypt_under_mask(cid_enc, cid_in, mask, cid_out);
|
|
}
|
|
#if 0
|
|
void picoquic_cid_free_encrypt_global_ctx(void ** v_cid_enc)
|
|
{
|
|
if (v_cid_enc != NULL) {
|
|
ptls_cipher_free((ptls_cipher_context_t *)v_cid_enc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Support for encrypted SNI (ESNI).
|
|
*/
|
|
|
|
/* Load the ESNI exchange keys. This function must be called at least once
|
|
* before setting up ESNI for the server.
|
|
* esni_key_elements should be declared as an array of ptls_key_exchange_context_t*
|
|
* of size less than *esni_key_count.
|
|
* The last element in the array should be a NULL pointer.
|
|
*/
|
|
|
|
int picoquic_esni_load_key(picoquic_quic_t * quic, char const * esni_key_file_name)
|
|
{
|
|
int ret = 0;
|
|
size_t esni_key_exchange_count = 0;
|
|
|
|
while (esni_key_exchange_count < 15 &&
|
|
quic->esni_key_exchange[esni_key_exchange_count] != 0)
|
|
esni_key_exchange_count++;
|
|
|
|
if (quic->esni_key_exchange[esni_key_exchange_count] != 0) {
|
|
DBG_PRINTF("Too many ESNI private key file, %zu already\n", esni_key_exchange_count);
|
|
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
|
|
} else {
|
|
ret = picoquic_exchange_context_from_file(esni_key_file_name, &quic->esni_key_exchange[esni_key_exchange_count]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_esni_load_rr(char const * esni_rr_file_name, uint8_t *esnikeys, size_t esnikeys_max, size_t *esnikeys_len)
|
|
{
|
|
FILE * fp = NULL;
|
|
int ret = 0;
|
|
|
|
*esnikeys_len = 0;
|
|
|
|
if ((fp = picoquic_file_open(esni_rr_file_name, "rb")) == NULL) {
|
|
fprintf(stderr, "failed to open file:%s\n", esni_rr_file_name);
|
|
ret = PICOQUIC_ERROR_INVALID_FILE;
|
|
}
|
|
else {
|
|
*esnikeys_len = fread(esnikeys, 1, esnikeys_max, fp);
|
|
if (*esnikeys_len == 0 || !feof(fp)) {
|
|
DBG_PRINTF("failed to load ESNI data from file:%s\n", esni_rr_file_name);
|
|
}
|
|
(void)picoquic_file_close(fp);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Setup ESNI by providing a set of RRDATA for the specified context.
|
|
* The "esni_rr_file_name" file contains the RRDATA of _ESNI RR published for the service in the DNS.
|
|
* The "esni_key_elements" contains a null terminated array of ptls_key_exchange_context_t*
|
|
* TODO: in theory, it should be possible to support multuple ESNI contexts in a single server.
|
|
* This would require repeated calls to the "init context" API, and an esni vector
|
|
* containing a longer null terminated list of esni pointers.
|
|
*/
|
|
|
|
static const size_t picoquic_esnikeys_max_rr = 65536;
|
|
|
|
int picoquic_esni_server_setup(picoquic_quic_t * quic, char const * esni_rr_file_name)
|
|
{
|
|
uint8_t * esnikeys = malloc(picoquic_esnikeys_max_rr);
|
|
size_t esnikeys_len;
|
|
ptls_context_t *ctx = (ptls_context_t *)quic->tls_master_ctx;
|
|
int ret = 0;
|
|
|
|
if (esnikeys != NULL) {
|
|
/* read esnikeys */
|
|
ret = picoquic_esni_load_rr(esni_rr_file_name, esnikeys, picoquic_esnikeys_max_rr, &esnikeys_len);
|
|
|
|
/* Install the ESNI data in the server context */
|
|
if (ret == 0) {
|
|
ctx->esni = malloc(sizeof(*ctx->esni) * 2);
|
|
|
|
if (ctx->esni != NULL) {
|
|
ctx->esni[1] = NULL;
|
|
ctx->esni[0] = malloc(sizeof(**ctx->esni));
|
|
}
|
|
|
|
if (ctx->esni == NULL || ctx->esni[0] == NULL) {
|
|
DBG_PRINTF("%s", "no memory for SNI allocation.\n");
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else {
|
|
if ((ret = ptls_esni_init_context(ctx, ctx->esni[0], ptls_iovec_init(esnikeys, esnikeys_len), quic->esni_key_exchange)) != 0) {
|
|
DBG_PRINTF("failed to parse ESNI data of file:%s:error=%d\n", esni_rr_file_name, ret);
|
|
}
|
|
}
|
|
}
|
|
free(esnikeys);
|
|
}
|
|
else {
|
|
DBG_PRINTF("failed to allocate memory(%zu) for parsing esni record\n", picoquic_esnikeys_max_rr);
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Setup the client side ESNI record, prior to using ESNI in a connection attempt.
|
|
*/
|
|
int picoquic_esni_client_from_file(picoquic_cnx_t * cnx, char const * esni_rr_file_name)
|
|
{
|
|
int ret = 0;
|
|
picoquic_tls_ctx_t* ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
|
|
uint8_t* esnikeys = malloc(picoquic_esnikeys_max_rr);
|
|
size_t esnikeys_len;
|
|
|
|
if (esnikeys != NULL) {
|
|
/* read esnikeys */
|
|
ret = picoquic_esni_load_rr(esni_rr_file_name, esnikeys, picoquic_esnikeys_max_rr, &esnikeys_len);
|
|
|
|
if (ret == 0 && esnikeys_len > 0 && esnikeys_len <= picoquic_esnikeys_max_rr){
|
|
ctx->handshake_properties.client.esni_keys.base = (uint8_t*)malloc(esnikeys_len);
|
|
if (ctx->handshake_properties.client.esni_keys.base == NULL) {
|
|
ctx->handshake_properties.client.esni_keys.len = 0;
|
|
DBG_PRINTF("%s", "no memory for SNI allocation.\n");
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else {
|
|
ctx->handshake_properties.client.esni_keys.len = esnikeys_len;
|
|
memcpy(ctx->handshake_properties.client.esni_keys.base, esnikeys, esnikeys_len);
|
|
}
|
|
}
|
|
else {
|
|
ctx->handshake_properties.client.esni_keys.len = 0;
|
|
ctx->handshake_properties.client.esni_keys.base = NULL;
|
|
if (ret != 0) {
|
|
ret = PICOQUIC_ERROR_INVALID_FILE;
|
|
}
|
|
}
|
|
free(esnikeys);
|
|
}
|
|
else {
|
|
DBG_PRINTF("failed to allocate memory(%zu) for parsing esni record\n", picoquic_esnikeys_max_rr);
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Access to ESNI data for tests
|
|
*/
|
|
|
|
uint16_t picoquic_esni_version(picoquic_cnx_t * cnx)
|
|
{
|
|
return(((picoquic_tls_ctx_t*)cnx->tls_ctx)->esni_version);
|
|
}
|
|
|
|
uint8_t * picoquic_esni_nonce(picoquic_cnx_t * cnx)
|
|
{
|
|
return(((picoquic_tls_ctx_t*)cnx->tls_ctx)->esni_nonce);
|
|
}
|
|
|
|
/* Retry Packet Protection.
|
|
* This is done by applying AES-GCM128 with a constant key and a NULL nonce,
|
|
* using an extension of the retry packet as authenticated data and a zero
|
|
* length content, computing a 16 bytes checksum. Or verifying it in the
|
|
* other direction.
|
|
*
|
|
* The retry protection key is stored in the Quic context. It is created on
|
|
* first use, and deleted when the context is deleted.
|
|
*/
|
|
|
|
void * picoquic_create_retry_protection_context(int is_enc, uint8_t * key)
|
|
{
|
|
return (void *)picoquic_setup_test_aead_context(is_enc, key);
|
|
}
|
|
|
|
void * picoquic_find_retry_protection_context(picoquic_cnx_t * cnx, int sending)
|
|
{
|
|
void * aead_ctx = NULL;
|
|
void ** aead_vector = (sending) ? cnx->quic->retry_integrity_sign_ctx : cnx->quic->retry_integrity_verify_ctx;
|
|
|
|
if (picoquic_supported_versions[cnx->version_index].version_retry_key != NULL) {
|
|
if (aead_vector == NULL) {
|
|
if (sending) {
|
|
cnx->quic->retry_integrity_sign_ctx = (void**)malloc(sizeof(void*)*picoquic_nb_supported_versions);
|
|
aead_vector = cnx->quic->retry_integrity_sign_ctx;
|
|
}
|
|
else {
|
|
cnx->quic->retry_integrity_verify_ctx = (void**)malloc(sizeof(void*)*picoquic_nb_supported_versions);
|
|
aead_vector = cnx->quic->retry_integrity_verify_ctx;
|
|
}
|
|
if (aead_vector != NULL) {
|
|
memset(aead_vector, 0, sizeof(void*)*picoquic_nb_supported_versions);
|
|
}
|
|
}
|
|
|
|
if (aead_vector != NULL) {
|
|
aead_ctx = aead_vector[cnx->version_index];
|
|
if (aead_ctx == NULL) {
|
|
aead_ctx = picoquic_create_retry_protection_context(sending, picoquic_supported_versions[cnx->version_index].version_retry_key);
|
|
aead_vector[cnx->version_index] = aead_ctx;
|
|
}
|
|
}
|
|
}
|
|
|
|
return aead_ctx;
|
|
}
|
|
|
|
static void ** picoquic_delete_one_retry_protection_context(void ** ctx)
|
|
{
|
|
if (ctx != NULL) {
|
|
for (size_t i = 0; i < picoquic_nb_supported_versions; i++) {
|
|
if (ctx[i] != NULL) {
|
|
picoquic_aead_free(ctx[i]);
|
|
}
|
|
}
|
|
free(ctx);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void picoquic_delete_retry_protection_contexts(picoquic_quic_t * quic)
|
|
{
|
|
quic->retry_integrity_sign_ctx = picoquic_delete_one_retry_protection_context(quic->retry_integrity_sign_ctx);
|
|
quic->retry_integrity_verify_ctx = picoquic_delete_one_retry_protection_context(quic->retry_integrity_verify_ctx);
|
|
}
|
|
|
|
static size_t picoquic_format_retry_protection_pseudo_packet(uint8_t * pseudo_packet, uint8_t * bytes, size_t byte_index, const picoquic_connection_id_t * odcid)
|
|
{
|
|
size_t pseudo_index = 0;
|
|
|
|
if (byte_index + odcid->id_len + 1 < PICOQUIC_MAX_PACKET_SIZE) {
|
|
pseudo_packet[pseudo_index++] = odcid->id_len;
|
|
memcpy(&pseudo_packet[pseudo_index], odcid->id, odcid->id_len);
|
|
pseudo_index += odcid->id_len;
|
|
memcpy(&pseudo_packet[pseudo_index], bytes, byte_index);
|
|
pseudo_index += byte_index;
|
|
}
|
|
|
|
return pseudo_index;
|
|
}
|
|
|
|
size_t picoquic_encode_retry_protection(void * integrity_aead, uint8_t * bytes, size_t bytes_max, size_t byte_index, const picoquic_connection_id_t * odcid)
|
|
{
|
|
size_t pseudo_index;
|
|
uint8_t pseudo_packet[PICOQUIC_MAX_PACKET_SIZE];
|
|
|
|
if (integrity_aead != NULL && byte_index + picoquic_aead_get_checksum_length(integrity_aead) < bytes_max &&
|
|
(pseudo_index = picoquic_format_retry_protection_pseudo_packet(pseudo_packet, bytes, byte_index, odcid)) > 0){
|
|
byte_index += picoquic_aead_encrypt_generic(bytes+byte_index, bytes+byte_index, 0, 0, pseudo_packet, pseudo_index, integrity_aead);
|
|
}
|
|
|
|
return byte_index;
|
|
}
|
|
|
|
int picoquic_verify_retry_protection(void * integrity_aead, uint8_t * bytes, size_t * length, size_t byte_index, const picoquic_connection_id_t * odcid)
|
|
{
|
|
int ret = PICOQUIC_ERROR_AEAD_CHECK;
|
|
size_t pseudo_index;
|
|
uint8_t pseudo_packet[PICOQUIC_MAX_PACKET_SIZE];
|
|
uint8_t decoded[PICOQUIC_MAX_PACKET_SIZE];
|
|
size_t checksum_length = picoquic_aead_get_checksum_length(integrity_aead);
|
|
|
|
if (byte_index + checksum_length < *length) {
|
|
*length -= checksum_length;
|
|
if ((pseudo_index = picoquic_format_retry_protection_pseudo_packet(pseudo_packet, bytes, *length, odcid)) > 0 &&
|
|
picoquic_aead_decrypt_generic(decoded, bytes + *length, checksum_length, 0, pseudo_packet, pseudo_index, integrity_aead) == 0) {
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|