From b46aad6df449445a9fc4aa7b32bd40005438e3f7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:18:05 +0200 Subject: Adding upstream version 2.9.5. Signed-off-by: Daniel Baumann --- src/ssl_sock.c | 8100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 8100 insertions(+) create mode 100644 src/ssl_sock.c (limited to 'src/ssl_sock.c') diff --git a/src/ssl_sock.c b/src/ssl_sock.c new file mode 100644 index 0000000..6fbabb4 --- /dev/null +++ b/src/ssl_sock.c @@ -0,0 +1,8100 @@ + +/* + * SSL/TLS transport layer over SOCK_STREAM sockets + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Acknowledgement: + * We'd like to specially thank the Stud project authors for a very clean + * and well documented code which helped us understand how the OpenSSL API + * ought to be used in non-blocking mode. This is one difficult part which + * is not easy to get from the OpenSSL doc, and reading the Stud code made + * it much more obvious than the examples in the OpenSSL package. Keep up + * the good works, guys ! + * + * Stud is an extremely efficient and scalable SSL/TLS proxy which combines + * particularly well with haproxy. For more info about this project, visit : + * https://github.com/bumptech/stud + * + */ + +/* Note: do NOT include openssl/xxx.h here, do it in openssl-compat.h */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ***** READ THIS before adding code here! ***** + * + * Due to API incompatibilities between multiple OpenSSL versions and their + * derivatives, it's often tempting to add macros to (re-)define certain + * symbols. Please do not do this here, and do it in common/openssl-compat.h + * exclusively so that the whole code consistently uses the same macros. + * + * Whenever possible if a macro is missing in certain versions, it's better + * to conditionally define it in openssl-compat.h than using lots of ifdefs. + */ + +int nb_engines = 0; + +static struct eb_root cert_issuer_tree = EB_ROOT; /* issuers tree from "issuers-chain-path" */ + +struct global_ssl global_ssl = { +#ifdef LISTEN_DEFAULT_CIPHERS + .listen_default_ciphers = LISTEN_DEFAULT_CIPHERS, +#endif +#ifdef CONNECT_DEFAULT_CIPHERS + .connect_default_ciphers = CONNECT_DEFAULT_CIPHERS, +#endif +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + .listen_default_ciphersuites = LISTEN_DEFAULT_CIPHERSUITES, + .connect_default_ciphersuites = CONNECT_DEFAULT_CIPHERSUITES, +#endif + .listen_default_ssloptions = BC_SSL_O_NONE, + .connect_default_ssloptions = SRV_SSL_O_NONE, + + .listen_default_sslmethods.flags = MC_SSL_O_ALL, + .listen_default_sslmethods.min = CONF_TLSV_NONE, + .listen_default_sslmethods.max = CONF_TLSV_NONE, + .connect_default_sslmethods.flags = MC_SSL_O_ALL, + .connect_default_sslmethods.min = CONF_TLSV_NONE, + .connect_default_sslmethods.max = CONF_TLSV_NONE, + +#ifdef DEFAULT_SSL_MAX_RECORD + .max_record = DEFAULT_SSL_MAX_RECORD, +#endif + .hard_max_record = 0, + .default_dh_param = SSL_DEFAULT_DH_PARAM, + .ctx_cache = DEFAULT_SSL_CTX_CACHE, + .capture_buffer_size = 0, + .extra_files = SSL_GF_ALL, + .extra_files_noext = 0, +#ifdef HAVE_SSL_KEYLOG + .keylog = 0, +#endif +#ifndef OPENSSL_NO_OCSP + .ocsp_update.delay_max = SSL_OCSP_UPDATE_DELAY_MAX, + .ocsp_update.delay_min = SSL_OCSP_UPDATE_DELAY_MIN, +#endif +}; + +static BIO_METHOD *ha_meth; + +DECLARE_STATIC_POOL(ssl_sock_ctx_pool, "ssl_sock_ctx", sizeof(struct ssl_sock_ctx)); + +DECLARE_STATIC_POOL(ssl_sock_client_sni_pool, "ssl_sock_client_sni", TLSEXT_MAXLEN_host_name + 1); + +/* ssl stats module */ +enum { + SSL_ST_SESS, + SSL_ST_REUSED_SESS, + SSL_ST_FAILED_HANDSHAKE, + + SSL_ST_STATS_COUNT /* must be the last member of the enum */ +}; + +static struct name_desc ssl_stats[] = { + [SSL_ST_SESS] = { .name = "ssl_sess", + .desc = "Total number of ssl sessions established" }, + [SSL_ST_REUSED_SESS] = { .name = "ssl_reused_sess", + .desc = "Total number of ssl sessions reused" }, + [SSL_ST_FAILED_HANDSHAKE] = { .name = "ssl_failed_handshake", + .desc = "Total number of failed handshake" }, +}; + +static struct ssl_counters { + long long sess; + long long reused_sess; + long long failed_handshake; +} ssl_counters; + +static void ssl_fill_stats(void *data, struct field *stats) +{ + struct ssl_counters *counters = data; + + stats[SSL_ST_SESS] = mkf_u64(FN_COUNTER, counters->sess); + stats[SSL_ST_REUSED_SESS] = mkf_u64(FN_COUNTER, counters->reused_sess); + stats[SSL_ST_FAILED_HANDSHAKE] = mkf_u64(FN_COUNTER, counters->failed_handshake); +} + +static struct stats_module ssl_stats_module = { + .name = "ssl", + .fill_stats = ssl_fill_stats, + .stats = ssl_stats, + .stats_count = SSL_ST_STATS_COUNT, + .counters = &ssl_counters, + .counters_size = sizeof(ssl_counters), + .domain_flags = MK_STATS_PROXY_DOMAIN(STATS_PX_CAP_FE|STATS_PX_CAP_LI|STATS_PX_CAP_BE|STATS_PX_CAP_SRV), + .clearable = 1, +}; + +INITCALL1(STG_REGISTER, stats_register_module, &ssl_stats_module); + +/* CLI context for "show tls-keys" */ +struct show_keys_ctx { + struct tls_keys_ref *next_ref; /* next reference to be dumped */ + int names_only; /* non-zero = only show file names */ + int next_index; /* next index to be dumped */ + int dump_entries; /* dump entries also */ + enum { + SHOW_KEYS_INIT = 0, + SHOW_KEYS_LIST, + SHOW_KEYS_DONE, + } state; /* phase of the current dump */ +}; + +/* ssl_sock_io_cb is exported to see it resolved in "show fd" */ +struct task *ssl_sock_io_cb(struct task *, void *, unsigned int); +static int ssl_sock_handshake(struct connection *conn, unsigned int flag); + +/* Methods to implement OpenSSL BIO */ +static int ha_ssl_write(BIO *h, const char *buf, int num) +{ + struct buffer tmpbuf; + struct ssl_sock_ctx *ctx; + uint flags; + int ret; + + ctx = BIO_get_data(h); + tmpbuf.size = num; + tmpbuf.area = (void *)(uintptr_t)buf; + tmpbuf.data = num; + tmpbuf.head = 0; + flags = (ctx->xprt_st & SSL_SOCK_SEND_MORE) ? CO_SFL_MSG_MORE : 0; + ret = ctx->xprt->snd_buf(ctx->conn, ctx->xprt_ctx, &tmpbuf, num, flags); + BIO_clear_retry_flags(h); + if (ret == 0 && !(ctx->conn->flags & (CO_FL_ERROR | CO_FL_SOCK_WR_SH))) { + BIO_set_retry_write(h); + ret = -1; + } + return ret; +} + +static int ha_ssl_gets(BIO *h, char *buf, int size) +{ + + return 0; +} + +static int ha_ssl_puts(BIO *h, const char *str) +{ + + return ha_ssl_write(h, str, strlen(str)); +} + +static int ha_ssl_read(BIO *h, char *buf, int size) +{ + struct buffer tmpbuf; + struct ssl_sock_ctx *ctx; + int ret; + + ctx = BIO_get_data(h); + tmpbuf.size = size; + tmpbuf.area = buf; + tmpbuf.data = 0; + tmpbuf.head = 0; + ret = ctx->xprt->rcv_buf(ctx->conn, ctx->xprt_ctx, &tmpbuf, size, 0); + BIO_clear_retry_flags(h); + if (ret == 0 && !(ctx->conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH))) { + BIO_set_retry_read(h); + ret = -1; + } + + return ret; +} + +static long ha_ssl_ctrl(BIO *h, int cmd, long arg1, void *arg2) +{ + int ret = 0; + switch (cmd) { + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + } + return ret; +} + +static int ha_ssl_new(BIO *h) +{ + BIO_set_init(h, 1); + BIO_set_data(h, NULL); + BIO_clear_flags(h, ~0); + return 1; +} + +static int ha_ssl_free(BIO *data) +{ + + return 1; +} + + +#if defined(USE_THREAD) && (HA_OPENSSL_VERSION_NUMBER < 0x10100000L) + +static HA_RWLOCK_T *ssl_rwlocks; + + +unsigned long ssl_id_function(void) +{ + return (unsigned long)tid; +} + +void ssl_locking_function(int mode, int n, const char * file, int line) +{ + if (mode & CRYPTO_LOCK) { + if (mode & CRYPTO_READ) + HA_RWLOCK_RDLOCK(SSL_LOCK, &ssl_rwlocks[n]); + else + HA_RWLOCK_WRLOCK(SSL_LOCK, &ssl_rwlocks[n]); + } + else { + if (mode & CRYPTO_READ) + HA_RWLOCK_RDUNLOCK(SSL_LOCK, &ssl_rwlocks[n]); + else + HA_RWLOCK_WRUNLOCK(SSL_LOCK, &ssl_rwlocks[n]); + } +} + +static int ssl_locking_init(void) +{ + int i; + + ssl_rwlocks = malloc(sizeof(HA_RWLOCK_T)*CRYPTO_num_locks()); + if (!ssl_rwlocks) + return -1; + + for (i = 0 ; i < CRYPTO_num_locks() ; i++) + HA_RWLOCK_INIT(&ssl_rwlocks[i]); + + CRYPTO_set_id_callback(ssl_id_function); + CRYPTO_set_locking_callback(ssl_locking_function); + + return 0; +} + +#endif + +__decl_thread(HA_SPINLOCK_T ckch_lock); + + + +/* mimic what X509_STORE_load_locations do with store_ctx */ +static int ssl_set_cert_crl_file(X509_STORE *store_ctx, char *path) +{ + X509_STORE *store = NULL; + struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0); + if (ca_e) + store = ca_e->ca_store; + if (store_ctx && store) { + int i; + X509_OBJECT *obj; + STACK_OF(X509_OBJECT) *objs = X509_STORE_get0_objects(store); + for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { + obj = sk_X509_OBJECT_value(objs, i); + switch (X509_OBJECT_get_type(obj)) { + case X509_LU_X509: + X509_STORE_add_cert(store_ctx, X509_OBJECT_get0_X509(obj)); + break; + case X509_LU_CRL: + X509_STORE_add_crl(store_ctx, X509_OBJECT_get0_X509_CRL(obj)); + break; + default: + break; + } + } + return 1; + } + return 0; +} + +/* SSL_CTX_load_verify_locations substitute, internally call X509_STORE_load_locations */ +static int ssl_set_verify_locations_file(SSL_CTX *ctx, char *path) +{ + X509_STORE *store_ctx = SSL_CTX_get_cert_store(ctx); + return ssl_set_cert_crl_file(store_ctx, path); +} + +/* + Extract CA_list from CA_file already in tree. + Duplicate ca_name is tracking with ebtree. It's simplify openssl compatibility. + Return a shared ca_list: SSL_dup_CA_list must be used before set it on SSL_CTX. +*/ +static STACK_OF(X509_NAME)* ssl_get_client_ca_file(char *path) +{ + struct ebmb_node *eb; + struct cafile_entry *ca_e; + + eb = ebst_lookup(&cafile_tree, path); + if (!eb) + return NULL; + ca_e = ebmb_entry(eb, struct cafile_entry, node); + + if (ca_e->ca_list == NULL) { + int i; + unsigned long key; + struct eb_root ca_name_tree = EB_ROOT; + struct eb64_node *node, *back; + struct { + struct eb64_node node; + X509_NAME *xname; + } *ca_name; + STACK_OF(X509_OBJECT) *objs; + STACK_OF(X509_NAME) *skn; + X509 *x; + X509_NAME *xn; + + skn = sk_X509_NAME_new_null(); + /* take x509 from cafile_tree */ + objs = X509_STORE_get0_objects(ca_e->ca_store); + for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { + x = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i)); + if (!x) + continue; + xn = X509_get_subject_name(x); + if (!xn) + continue; + /* Check for duplicates. */ + key = X509_NAME_hash(xn); + for (node = eb64_lookup(&ca_name_tree, key), ca_name = NULL; + node && ca_name == NULL; + node = eb64_next(node)) { + ca_name = container_of(node, typeof(*ca_name), node); + if (X509_NAME_cmp(xn, ca_name->xname) != 0) + ca_name = NULL; + } + /* find a duplicate */ + if (ca_name) + continue; + ca_name = calloc(1, sizeof *ca_name); + xn = X509_NAME_dup(xn); + if (!ca_name || + !xn || + !sk_X509_NAME_push(skn, xn)) { + free(ca_name); + X509_NAME_free(xn); + sk_X509_NAME_pop_free(skn, X509_NAME_free); + sk_X509_NAME_free(skn); + skn = NULL; + break; + } + ca_name->node.key = key; + ca_name->xname = xn; + eb64_insert(&ca_name_tree, &ca_name->node); + } + ca_e->ca_list = skn; + /* remove temporary ca_name tree */ + node = eb64_first(&ca_name_tree); + while (node) { + ca_name = container_of(node, typeof(*ca_name), node); + back = eb64_next(node); + eb64_delete(node); + free(ca_name); + node = back; + } + } + return ca_e->ca_list; +} + +struct pool_head *pool_head_ssl_capture __read_mostly = NULL; +int ssl_capture_ptr_index = -1; +int ssl_app_data_index = -1; +#ifdef USE_QUIC +int ssl_qc_app_data_index = -1; +#endif /* USE_QUIC */ + +#ifdef HAVE_SSL_KEYLOG +int ssl_keylog_index = -1; +struct pool_head *pool_head_ssl_keylog __read_mostly = NULL; +struct pool_head *pool_head_ssl_keylog_str __read_mostly = NULL; +#endif + +int ssl_client_crt_ref_index = -1; + +/* Used to store the client's SNI in case of ClientHello callback error */ +int ssl_client_sni_index = -1; + +#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0) +struct list tlskeys_reference = LIST_HEAD_INIT(tlskeys_reference); +#endif + +#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE) +unsigned int openssl_engines_initialized; +struct list openssl_engines = LIST_HEAD_INIT(openssl_engines); +struct ssl_engine_list { + struct list list; + ENGINE *e; +}; +#endif + +#ifdef HAVE_SSL_PROVIDERS +struct list openssl_providers = LIST_HEAD_INIT(openssl_providers); +struct ssl_provider_list { + struct list list; + OSSL_PROVIDER *provider; +}; +#endif + +#ifndef OPENSSL_NO_DH +static int ssl_dh_ptr_index = -1; +static HASSL_DH *global_dh = NULL; +static HASSL_DH *local_dh_1024 = NULL; +static HASSL_DH *local_dh_2048 = NULL; +static HASSL_DH *local_dh_4096 = NULL; +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) +static DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen); +#else +static void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey); +#endif +#endif /* OPENSSL_NO_DH */ + +#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) +/* X509V3 Extensions that will be added on generated certificates */ +#define X509V3_EXT_SIZE 5 +static char *x509v3_ext_names[X509V3_EXT_SIZE] = { + "basicConstraints", + "nsComment", + "subjectKeyIdentifier", + "authorityKeyIdentifier", + "keyUsage", +}; +static char *x509v3_ext_values[X509V3_EXT_SIZE] = { + "CA:FALSE", + "\"OpenSSL Generated Certificate\"", + "hash", + "keyid,issuer:always", + "nonRepudiation,digitalSignature,keyEncipherment" +}; +/* LRU cache to store generated certificate */ +static struct lru64_head *ssl_ctx_lru_tree = NULL; +static unsigned int ssl_ctx_lru_seed = 0; +static unsigned int ssl_ctx_serial; +__decl_rwlock(ssl_ctx_lru_rwlock); + +#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME + +/* The order here matters for picking a default context, + * keep the most common keytype at the bottom of the list + */ +const char *SSL_SOCK_KEYTYPE_NAMES[] = { + "dsa", + "ecdsa", + "rsa" +}; + +static struct shared_context *ssl_shctx = NULL; /* ssl shared session cache */ +static struct eb_root *sh_ssl_sess_tree; /* ssl shared session tree */ + +/* Dedicated callback functions for heartbeat and clienthello. + */ +#ifdef TLS1_RT_HEARTBEAT +static void ssl_sock_parse_heartbeat(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl); +#endif +static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl); + +#ifdef HAVE_SSL_KEYLOG +static void ssl_init_keylog(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl); +#endif + +/* List head of all registered SSL/TLS protocol message callbacks. */ +struct list ssl_sock_msg_callbacks = LIST_HEAD_INIT(ssl_sock_msg_callbacks); + +/* Registers the function in order to be called on SSL/TLS protocol + * message processing. It will return 0 if the function is not set + * or if it fails to allocate memory. + */ +int ssl_sock_register_msg_callback(ssl_sock_msg_callback_func func) +{ + struct ssl_sock_msg_callback *cbk; + + if (!func) + return 0; + + cbk = calloc(1, sizeof(*cbk)); + if (!cbk) { + ha_alert("out of memory in ssl_sock_register_msg_callback().\n"); + return 0; + } + + cbk->func = func; + + LIST_APPEND(&ssl_sock_msg_callbacks, &cbk->list); + + return 1; +} + +/* Used to register dedicated SSL/TLS protocol message callbacks. + */ +static int ssl_sock_register_msg_callbacks(void) +{ +#ifdef TLS1_RT_HEARTBEAT + if (!ssl_sock_register_msg_callback(ssl_sock_parse_heartbeat)) + return ERR_ABORT; +#endif + if (global_ssl.capture_buffer_size > 0) { + if (!ssl_sock_register_msg_callback(ssl_sock_parse_clienthello)) + return ERR_ABORT; + } +#ifdef HAVE_SSL_KEYLOG + if (global_ssl.keylog > 0) { + if (!ssl_sock_register_msg_callback(ssl_init_keylog)) + return ERR_ABORT; + } +#endif +#ifdef USE_QUIC_OPENSSL_COMPAT + if (!ssl_sock_register_msg_callback(quic_tls_compat_msg_callback)) + return ERR_ABORT; +#endif + + return ERR_NONE; +} + +/* Used to free all SSL/TLS protocol message callbacks that were + * registered by using ssl_sock_register_msg_callback(). + */ +static void ssl_sock_unregister_msg_callbacks(void) +{ + struct ssl_sock_msg_callback *cbk, *cbkback; + + list_for_each_entry_safe(cbk, cbkback, &ssl_sock_msg_callbacks, list) { + LIST_DELETE(&cbk->list); + free(cbk); + } +} + +static struct ssl_sock_ctx *ssl_sock_get_ctx(struct connection *conn) +{ + if (!conn || conn->xprt != xprt_get(XPRT_SSL) || !conn->xprt_ctx) + return NULL; + + return (struct ssl_sock_ctx *)conn->xprt_ctx; +} + +SSL *ssl_sock_get_ssl_object(struct connection *conn) +{ + struct ssl_sock_ctx *ctx = conn_get_ssl_sock_ctx(conn); + + return ctx ? ctx->ssl : NULL; +} +/* + * This function gives the detail of the SSL error. It is used only + * if the debug mode and the verbose mode are activated. It dump all + * the SSL error until the stack was empty. + */ +static forceinline void ssl_sock_dump_errors(struct connection *conn, + struct quic_conn *qc) +{ + unsigned long ret; + + if (unlikely(global.mode & MODE_DEBUG)) { + while(1) { + const char *func = NULL; + ERR_peek_error_func(&func); + + ret = ERR_get_error(); + if (ret == 0) + return; + if (conn) { + fprintf(stderr, "fd[%#x] OpenSSL error[0x%lx] %s: %s\n", + conn_fd(conn), ret, + func, ERR_reason_error_string(ret)); + } +#ifdef USE_QUIC + else { + /* TODO: we are not sure is always initialized for QUIC connections */ + fprintf(stderr, "qc @%p OpenSSL error[0x%lx] %s: %s\n", qc, ret, + func, ERR_reason_error_string(ret)); + } +#endif + } + } +} + + +#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE) +int ssl_init_single_engine(const char *engine_id, const char *def_algorithms) +{ + int err_code = ERR_ABORT; + ENGINE *engine; + struct ssl_engine_list *el; + + /* grab the structural reference to the engine */ + engine = ENGINE_by_id(engine_id); + if (engine == NULL) { + ha_alert("ssl-engine %s: failed to get structural reference\n", engine_id); + goto fail_get; + } + + if (!ENGINE_init(engine)) { + /* the engine couldn't initialise, release it */ + ha_alert("ssl-engine %s: failed to initialize\n", engine_id); + goto fail_init; + } + + if (ENGINE_set_default_string(engine, def_algorithms) == 0) { + ha_alert("ssl-engine %s: failed on ENGINE_set_default_string\n", engine_id); + goto fail_set_method; + } + + el = calloc(1, sizeof(*el)); + if (!el) + goto fail_alloc; + el->e = engine; + LIST_INSERT(&openssl_engines, &el->list); + nb_engines++; + if (global_ssl.async) + global.ssl_used_async_engines = nb_engines; + return 0; + +fail_alloc: +fail_set_method: + /* release the functional reference from ENGINE_init() */ + ENGINE_finish(engine); + +fail_init: + /* release the structural reference from ENGINE_by_id() */ + ENGINE_free(engine); + +fail_get: + return err_code; +} +#endif + +#ifdef HAVE_SSL_PROVIDERS +int ssl_init_provider(const char *provider_name) +{ + int err_code = ERR_ABORT; + struct ssl_provider_list *prov = NULL; + + prov = calloc(1, sizeof(*prov)); + if (!prov) { + ha_alert("ssl-provider %s: memory allocation failure\n", provider_name); + goto error; + } + + if ((prov->provider = OSSL_PROVIDER_load(NULL, provider_name)) == NULL) { + ha_alert("ssl-provider %s: unknown provider\n", provider_name); + goto error; + } + + LIST_INSERT(&openssl_providers, &prov->list); + + return 0; + +error: + ha_free(&prov); + return err_code; +} +#endif /* HAVE_SSL_PROVIDERS */ + +#ifdef SSL_MODE_ASYNC +/* + * openssl async fd handler + */ +void ssl_async_fd_handler(int fd) +{ + struct ssl_sock_ctx *ctx = fdtab[fd].owner; + + /* fd is an async enfine fd, we must stop + * to poll this fd until it is requested + */ + fd_stop_recv(fd); + fd_cant_recv(fd); + + /* crypto engine is available, let's notify the associated + * connection that it can pursue its processing. + */ + tasklet_wakeup(ctx->wait_event.tasklet); +} + +/* + * openssl async delayed SSL_free handler + */ +void ssl_async_fd_free(int fd) +{ + SSL *ssl = fdtab[fd].owner; + OSSL_ASYNC_FD all_fd[32]; + size_t num_all_fds = 0; + int i; + + /* We suppose that the async job for a same SSL * + * are serialized. So if we are awake it is + * because the running job has just finished + * and we can remove all async fds safely + */ + SSL_get_all_async_fds(ssl, NULL, &num_all_fds); + if (num_all_fds > 32) { + send_log(NULL, LOG_EMERG, "haproxy: openssl returns too many async fds. It seems a bug. Process may crash\n"); + return; + } + + SSL_get_all_async_fds(ssl, all_fd, &num_all_fds); + for (i=0 ; i < num_all_fds ; i++) { + /* We want to remove the fd from the fdtab + * but we flag it to disown because the + * close is performed by the engine itself + */ + fdtab[all_fd[i]].state |= FD_DISOWN; + fd_delete(all_fd[i]); + } + + /* Now we can safely call SSL_free, no more pending job in engines */ + SSL_free(ssl); + _HA_ATOMIC_DEC(&global.sslconns); + _HA_ATOMIC_DEC(&jobs); +} +/* + * function used to manage a returned SSL_ERROR_WANT_ASYNC + * and enable/disable polling for async fds + */ +static inline void ssl_async_process_fds(struct ssl_sock_ctx *ctx) +{ + OSSL_ASYNC_FD add_fd[32]; + OSSL_ASYNC_FD del_fd[32]; + SSL *ssl = ctx->ssl; + size_t num_add_fds = 0; + size_t num_del_fds = 0; + int i; + + SSL_get_changed_async_fds(ssl, NULL, &num_add_fds, NULL, + &num_del_fds); + if (num_add_fds > 32 || num_del_fds > 32) { + send_log(NULL, LOG_EMERG, "haproxy: openssl returns too many async fds. It seems a bug. Process may crash\n"); + return; + } + + SSL_get_changed_async_fds(ssl, add_fd, &num_add_fds, del_fd, &num_del_fds); + + /* We remove unused fds from the fdtab */ + for (i=0 ; i < num_del_fds ; i++) { + /* We want to remove the fd from the fdtab + * but we flag it to disown because the + * close is performed by the engine itself + */ + fdtab[del_fd[i]].state |= FD_DISOWN; + fd_delete(del_fd[i]); + } + + /* We add new fds to the fdtab */ + for (i=0 ; i < num_add_fds ; i++) { + fd_insert(add_fd[i], ctx, ssl_async_fd_handler, tgid, ti->ltid_bit); + } + + num_add_fds = 0; + SSL_get_all_async_fds(ssl, NULL, &num_add_fds); + if (num_add_fds > 32) { + send_log(NULL, LOG_EMERG, "haproxy: openssl returns too many async fds. It seems a bug. Process may crash\n"); + return; + } + + /* We activate the polling for all known async fds */ + SSL_get_all_async_fds(ssl, add_fd, &num_add_fds); + for (i=0 ; i < num_add_fds ; i++) { + fd_want_recv(add_fd[i]); + /* To ensure that the fd cache won't be used + * We'll prefer to catch a real RD event + * because handling an EAGAIN on this fd will + * result in a context switch and also + * some engines uses a fd in blocking mode. + */ + fd_cant_recv(add_fd[i]); + } + +} +#endif + + +/* + * Initialize an HMAC context using the and parameters. + * Returns -1 in case of error, 1 otherwise. + */ +static int ssl_hmac_init(MAC_CTX *hctx, unsigned char *key, int key_len, const EVP_MD *md) +{ +#ifdef HAVE_OSSL_PARAM + OSSL_PARAM params[3]; + + params[0] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, key, key_len); + params[1] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, (char*)EVP_MD_name(md), 0); + params[2] = OSSL_PARAM_construct_end(); + if (EVP_MAC_CTX_set_params(hctx, params) == 0) + return -1; /* error in mac initialisation */ + +#else + HMAC_Init_ex(hctx, key, key_len, md, NULL); +#endif + return 1; +} + +#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0) + +static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned char *iv, EVP_CIPHER_CTX *ectx, MAC_CTX *hctx, int enc) +{ + struct tls_keys_ref *ref = NULL; + union tls_sess_key *keys; + int head; + int i; + int ret = -1; /* error by default */ + struct connection *conn = SSL_get_ex_data(s, ssl_app_data_index); +#ifdef USE_QUIC + struct quic_conn *qc = SSL_get_ex_data(s, ssl_qc_app_data_index); +#endif + + if (conn) + ref = __objt_listener(conn->target)->bind_conf->keys_ref; +#ifdef USE_QUIC + else if (qc) + ref = qc->li->bind_conf->keys_ref; +#endif + + if (!ref) { + /* must never happen */ + ABORT_NOW(); + } + + HA_RWLOCK_RDLOCK(TLSKEYS_REF_LOCK, &ref->lock); + + keys = ref->tlskeys; + head = ref->tls_ticket_enc_index; + + if (enc) { + memcpy(key_name, keys[head].name, 16); + + if(!RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH)) + goto end; + + if (ref->key_size_bits == 128) { + + if(!EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[head].key_128.aes_key, iv)) + goto end; + + if (ssl_hmac_init(hctx, keys[head].key_128.hmac_key, 16, TLS_TICKET_HASH_FUNCT()) < 0) + goto end; + ret = 1; + } + else if (ref->key_size_bits == 256 ) { + + if(!EVP_EncryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, keys[head].key_256.aes_key, iv)) + goto end; + + if (ssl_hmac_init(hctx, keys[head].key_256.hmac_key, 32, TLS_TICKET_HASH_FUNCT()) < 0) + goto end; + ret = 1; + } + } else { + for (i = 0; i < TLS_TICKETS_NO; i++) { + if (!memcmp(key_name, keys[(head + i) % TLS_TICKETS_NO].name, 16)) + goto found; + } + ret = 0; + goto end; + + found: + if (ref->key_size_bits == 128) { + if (ssl_hmac_init(hctx, keys[(head + i) % TLS_TICKETS_NO].key_128.hmac_key, 16, TLS_TICKET_HASH_FUNCT()) < 0) + goto end; + if(!EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[(head + i) % TLS_TICKETS_NO].key_128.aes_key, iv)) + goto end; + /* 2 for key renewal, 1 if current key is still valid */ + ret = i ? 2 : 1; + } + else if (ref->key_size_bits == 256) { + if (ssl_hmac_init(hctx, keys[(head + i) % TLS_TICKETS_NO].key_256.hmac_key, 32, TLS_TICKET_HASH_FUNCT()) < 0) + goto end; + if(!EVP_DecryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, keys[(head + i) % TLS_TICKETS_NO].key_256.aes_key, iv)) + goto end; + /* 2 for key renewal, 1 if current key is still valid */ + ret = i ? 2 : 1; + } + } + + end: + HA_RWLOCK_RDUNLOCK(TLSKEYS_REF_LOCK, &ref->lock); + return ret; +} + +struct tls_keys_ref *tlskeys_ref_lookup(const char *filename) +{ + struct tls_keys_ref *ref; + + list_for_each_entry(ref, &tlskeys_reference, list) + if (ref->filename && strcmp(filename, ref->filename) == 0) + return ref; + return NULL; +} + +struct tls_keys_ref *tlskeys_ref_lookupid(int unique_id) +{ + struct tls_keys_ref *ref; + + list_for_each_entry(ref, &tlskeys_reference, list) + if (ref->unique_id == unique_id) + return ref; + return NULL; +} + +/* Update the key into ref: if keysize doesn't + * match existing ones, this function returns -1 + * else it returns 0 on success. + */ +int ssl_sock_update_tlskey_ref(struct tls_keys_ref *ref, + struct buffer *tlskey) +{ + if (ref->key_size_bits == 128) { + if (tlskey->data != sizeof(struct tls_sess_key_128)) + return -1; + } + else if (ref->key_size_bits == 256) { + if (tlskey->data != sizeof(struct tls_sess_key_256)) + return -1; + } + else + return -1; + + HA_RWLOCK_WRLOCK(TLSKEYS_REF_LOCK, &ref->lock); + memcpy((char *) (ref->tlskeys + ((ref->tls_ticket_enc_index + 2) % TLS_TICKETS_NO)), + tlskey->area, tlskey->data); + ref->tls_ticket_enc_index = (ref->tls_ticket_enc_index + 1) % TLS_TICKETS_NO; + HA_RWLOCK_WRUNLOCK(TLSKEYS_REF_LOCK, &ref->lock); + + return 0; +} + +int ssl_sock_update_tlskey(char *filename, struct buffer *tlskey, char **err) +{ + struct tls_keys_ref *ref = tlskeys_ref_lookup(filename); + + if(!ref) { + memprintf(err, "Unable to locate the referenced filename: %s", filename); + return 1; + } + if (ssl_sock_update_tlskey_ref(ref, tlskey) < 0) { + memprintf(err, "Invalid key size"); + return 1; + } + + return 0; +} + +/* This function finalize the configuration parsing. Its set all the + * automatic ids. It's called just after the basic checks. It returns + * 0 on success otherwise ERR_*. + */ +static int tlskeys_finalize_config(void) +{ + int i = 0; + struct tls_keys_ref *ref, *ref2, *ref3; + struct list tkr = LIST_HEAD_INIT(tkr); + + list_for_each_entry(ref, &tlskeys_reference, list) { + if (ref->unique_id == -1) { + /* Look for the first free id. */ + while (1) { + list_for_each_entry(ref2, &tlskeys_reference, list) { + if (ref2->unique_id == i) { + i++; + break; + } + } + if (&ref2->list == &tlskeys_reference) + break; + } + + /* Uses the unique id and increment it for the next entry. */ + ref->unique_id = i; + i++; + } + } + + /* This sort the reference list by id. */ + list_for_each_entry_safe(ref, ref2, &tlskeys_reference, list) { + LIST_DELETE(&ref->list); + list_for_each_entry(ref3, &tkr, list) { + if (ref->unique_id < ref3->unique_id) { + LIST_APPEND(&ref3->list, &ref->list); + break; + } + } + if (&ref3->list == &tkr) + LIST_APPEND(&tkr, &ref->list); + } + + /* swap root */ + LIST_SPLICE(&tlskeys_reference, &tkr); + return ERR_NONE; +} +#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */ + + +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL) +/* + * This function enables the handling of OCSP status extension on 'ctx' if a + * ocsp_response buffer was found in the cert_key_and_chain. To enable OCSP + * status extension, the issuer's certificate is mandatory. It should be + * present in ckch->ocsp_issuer. + * + * In addition, the ckch->ocsp_reponse buffer is loaded as a DER format of an + * OCSP response. If file is empty or content is not a valid OCSP response, + * OCSP status extension is enabled but OCSP response is ignored (a warning is + * displayed). + * + * Returns 1 if no ".ocsp" file found, 0 if OCSP status extension is + * successfully enabled, or -1 in other error case. + */ +static int ssl_sock_load_ocsp(const char *path, SSL_CTX *ctx, struct ckch_data *data, STACK_OF(X509) *chain) +{ + X509 *x, *issuer; + int i, ret = -1; + struct certificate_ocsp *ocsp = NULL, *iocsp; + char *warn = NULL; + unsigned char *p; +#ifndef USE_OPENSSL_WOLFSSL +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) + int (*callback) (SSL *, void *); +#else + void (*callback) (void); +#endif +#else + tlsextStatusCb callback; +#endif + struct buffer *ocsp_uri = get_trash_chunk(); + char *err = NULL; + size_t path_len; + int inc_refcount_store = 0; + + x = data->cert; + if (!x) + goto out; + + ssl_ocsp_get_uri_from_cert(x, ocsp_uri, &err); + /* We should have an "OCSP URI" field in order for auto update to work. */ + if (data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON && b_data(ocsp_uri) == 0) + goto out; + + /* In case of ocsp update mode set to 'on', this function might be + * called with no known ocsp response. If no ocsp uri can be found in + * the certificate, nothing needs to be done here. */ + if (!data->ocsp_response && !data->ocsp_cid) { + if (data->ocsp_update_mode != SSL_SOCK_OCSP_UPDATE_ON || b_data(ocsp_uri) == 0) { + ret = 0; + goto out; + } + } + + issuer = data->ocsp_issuer; + /* take issuer from chain over ocsp_issuer, is what is done historicaly */ + if (chain) { + /* check if one of the certificate of the chain is the issuer */ + for (i = 0; i < sk_X509_num(chain); i++) { + X509 *ti = sk_X509_value(chain, i); + if (X509_check_issued(ti, x) == X509_V_OK) { + issuer = ti; + break; + } + } + } + if (!issuer) + goto out; + + if (!data->ocsp_cid) { + data->ocsp_cid = OCSP_cert_to_id(0, x, issuer); + inc_refcount_store = 1; + } + if (!data->ocsp_cid) + goto out; + + i = i2d_OCSP_CERTID(data->ocsp_cid, NULL); + if (!i || (i > OCSP_MAX_CERTID_ASN1_LENGTH)) + goto out; + + path_len = strlen(path); + ocsp = calloc(1, sizeof(*ocsp) + path_len + 1); + if (!ocsp) + goto out; + + p = ocsp->key_data; + ocsp->key_length = i2d_OCSP_CERTID(data->ocsp_cid, &p); + + HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock); + iocsp = (struct certificate_ocsp *)ebmb_insert(&cert_ocsp_tree, &ocsp->key, OCSP_MAX_CERTID_ASN1_LENGTH); + if (iocsp == ocsp) + ocsp = NULL; + +#ifndef SSL_CTX_get_tlsext_status_cb +# define SSL_CTX_get_tlsext_status_cb(ctx, cb) \ + *cb = (void (*) (void))ctx->tlsext_status_cb; +#endif + SSL_CTX_get_tlsext_status_cb(ctx, &callback); + + if (inc_refcount_store) + iocsp->refcount_store++; + + if (!callback) { + struct ocsp_cbk_arg *cb_arg; + EVP_PKEY *pkey; + + cb_arg = calloc(1, sizeof(*cb_arg)); + if (!cb_arg) + goto out; + + cb_arg->is_single = 1; + cb_arg->s_ocsp = iocsp; + iocsp->refcount_instance++; + + pkey = X509_get_pubkey(x); + cb_arg->single_kt = EVP_PKEY_base_id(pkey); + EVP_PKEY_free(pkey); + + SSL_CTX_set_tlsext_status_cb(ctx, ssl_sock_ocsp_stapling_cbk); + SSL_CTX_set_ex_data(ctx, ocsp_ex_index, cb_arg); /* we use the ex_data instead of the cb_arg function here, so we can use the cleanup callback to free */ + + } else { + /* + * If the ctx has a status CB, then we have previously set an OCSP staple for this ctx + * Update that cb_arg with the new cert's staple + */ + struct ocsp_cbk_arg *cb_arg; + struct certificate_ocsp *tmp_ocsp; + int index; + int key_type; + EVP_PKEY *pkey; + + cb_arg = SSL_CTX_get_ex_data(ctx, ocsp_ex_index); + + /* + * The following few lines will convert cb_arg from a single ocsp to multi ocsp + * the order of operations below matter, take care when changing it + */ + tmp_ocsp = cb_arg->s_ocsp; + index = ssl_sock_get_ocsp_arg_kt_index(cb_arg->single_kt); + cb_arg->s_ocsp = NULL; + cb_arg->m_ocsp[index] = tmp_ocsp; + cb_arg->is_single = 0; + cb_arg->single_kt = 0; + + pkey = X509_get_pubkey(x); + key_type = EVP_PKEY_base_id(pkey); + EVP_PKEY_free(pkey); + + index = ssl_sock_get_ocsp_arg_kt_index(key_type); + if (index >= 0 && !cb_arg->m_ocsp[index]) { + cb_arg->m_ocsp[index] = iocsp; + iocsp->refcount_instance++; + } + } + HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock); + + ret = 0; + + warn = NULL; + if (data->ocsp_response && ssl_sock_load_ocsp_response(data->ocsp_response, iocsp, data->ocsp_cid, &warn)) { + memprintf(&warn, "Loading: %s. Content will be ignored", warn ? warn : "failure"); + ha_warning("%s.\n", warn); + } + + + /* Do not insert the same certificate_ocsp structure in the + * update tree more than once. */ + if (!ocsp) { + /* Issuer certificate is not included in the certificate + * chain, it will have to be treated separately during + * ocsp response validation. */ + if (issuer == data->ocsp_issuer) { + iocsp->issuer = issuer; + X509_up_ref(issuer); + } + if (data->chain) + iocsp->chain = X509_chain_up_ref(data->chain); + + iocsp->uri = calloc(1, sizeof(*iocsp->uri)); + if (!chunk_dup(iocsp->uri, ocsp_uri)) { + ha_free(&iocsp->uri); + goto out; + } + + /* Note: if we arrive here, ocsp==NULL because iocsp==ocsp + * after the ebmb_insert(), which indicates that we've + * just inserted this new node and that it's the one for + * which we previously allocated enough room for path_len+1 + * chars. + */ + memcpy(iocsp->path, path, path_len + 1); + + if (data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON) { + ssl_ocsp_update_insert(iocsp); + /* If we are during init the update task is not + * scheduled yet so a wakeup won't do anything. + * Otherwise, if the OCSP was added through the CLI, we + * wake the task up to manage the case of a new entry + * that needs to be updated before the previous first + * entry. + */ + if (ocsp_update_task) + task_wakeup(ocsp_update_task, TASK_WOKEN_MSG); + } + } else if (iocsp->uri && data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON) { + /* This unlikely case can happen if a series of "del ssl + * crt-list" / "add ssl crt-list" commands are made on the CLI. + * In such a case, the OCSP response tree entry will be created + * prior to the activation of the ocsp auto update and in such a + * case we must "force" insertion in the auto update tree. + */ + if (iocsp->next_update.node.leaf_p == NULL) { + ssl_ocsp_update_insert(iocsp); + /* If we are during init the update task is not + * scheduled yet so a wakeup won't do anything. + * Otherwise, if the OCSP was added through the CLI, we + * wake the task up to manage the case of a new entry + * that needs to be updated before the previous first + * entry. + */ + if (ocsp_update_task) + task_wakeup(ocsp_update_task, TASK_WOKEN_MSG); + } + } + +out: + if (ret && data->ocsp_cid) { + OCSP_CERTID_free(data->ocsp_cid); + data->ocsp_cid = NULL; + } + + if (!ret && data->ocsp_response) { + ha_free(&data->ocsp_response->area); + ha_free(&data->ocsp_response); + } + + if (ocsp) + ssl_sock_free_ocsp(ocsp); + + if (warn) + free(warn); + + free(err); + + return ret; +} + +#endif + +#ifdef OPENSSL_IS_BORINGSSL +static int ssl_sock_load_ocsp(const char *path, SSL_CTX *ctx, struct ckch_data *data, STACK_OF(X509) *chain) +{ + return SSL_CTX_set_ocsp_response(ctx, (const uint8_t *)ckch->ocsp_response->area, ckch->ocsp_response->data); +} +#endif + + +#ifdef HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT + +#define CT_EXTENSION_TYPE 18 + +int sctl_ex_index = -1; + +int ssl_sock_sctl_add_cbk(SSL *ssl, unsigned ext_type, const unsigned char **out, size_t *outlen, int *al, void *add_arg) +{ + struct buffer *sctl = add_arg; + + *out = (unsigned char *) sctl->area; + *outlen = sctl->data; + + return 1; +} + +int ssl_sock_sctl_parse_cbk(SSL *s, unsigned int ext_type, const unsigned char *in, size_t inlen, int *al, void *parse_arg) +{ + return 1; +} + +static int ssl_sock_load_sctl(SSL_CTX *ctx, struct buffer *sctl) +{ + int ret = -1; + + if (!SSL_CTX_add_server_custom_ext(ctx, CT_EXTENSION_TYPE, ssl_sock_sctl_add_cbk, NULL, sctl, ssl_sock_sctl_parse_cbk, NULL)) + goto out; + + SSL_CTX_set_ex_data(ctx, sctl_ex_index, sctl); + + ret = 0; + +out: + return ret; +} + +#endif + +void ssl_sock_infocbk(const SSL *ssl, int where, int ret) +{ + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); +#ifdef USE_QUIC + struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); +#endif /* USE_QUIC */ + struct ssl_sock_ctx *ctx = NULL; + + BIO *write_bio; + (void)ret; /* shut gcc stupid warning */ + + if (conn) + ctx = conn_get_ssl_sock_ctx(conn); +#ifdef USE_QUIC + else if (qc) + ctx = qc->xprt_ctx; +#endif /* USE_QUIC */ + + if (!ctx) { + /* must never happen */ + ABORT_NOW(); + return; + } + +#ifndef SSL_OP_NO_RENEGOTIATION + /* Please note that BoringSSL defines this macro to zero so don't + * change this to #if and do not assign a default value to this macro! + */ + if (where & SSL_CB_HANDSHAKE_START) { + /* Disable renegotiation (CVE-2009-3555) */ + if (conn && (conn->flags & (CO_FL_WAIT_L6_CONN | CO_FL_EARLY_SSL_HS | CO_FL_EARLY_DATA)) == 0) { + conn->flags |= CO_FL_ERROR; + conn->err_code = CO_ER_SSL_RENEG; + } + } +#endif + + if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) { + if (!(ctx->xprt_st & SSL_SOCK_ST_FL_16K_WBFSIZE)) { + /* Long certificate chains optimz + If write and read bios are different, we + consider that the buffering was activated, + so we rise the output buffer size from 4k + to 16k */ + write_bio = SSL_get_wbio(ssl); + if (write_bio != SSL_get_rbio(ssl)) { + BIO_set_write_buffer_size(write_bio, 16384); + ctx->xprt_st |= SSL_SOCK_ST_FL_16K_WBFSIZE; + } + } + } +} + +/* Callback is called for each certificate of the chain during a verify + ok is set to 1 if preverify detect no error on current certificate. + Returns 0 to break the handshake, 1 otherwise. */ +int ssl_sock_bind_verifycbk(int ok, X509_STORE_CTX *x_store) +{ + SSL *ssl; + struct connection *conn; + struct ssl_sock_ctx *ctx = NULL; + int err, depth; + X509 *client_crt; + STACK_OF(X509) *certs; + struct bind_conf *bind_conf = NULL; + struct quic_conn *qc = NULL; + + ssl = X509_STORE_CTX_get_ex_data(x_store, SSL_get_ex_data_X509_STORE_CTX_idx()); + conn = SSL_get_ex_data(ssl, ssl_app_data_index); + client_crt = SSL_get_ex_data(ssl, ssl_client_crt_ref_index); + + if (conn) { + bind_conf = __objt_listener(conn->target)->bind_conf; + ctx = __conn_get_ssl_sock_ctx(conn); + } +#ifdef USE_QUIC + else { + qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); + BUG_ON(!qc); /* Must never happen */ + bind_conf = qc->li->bind_conf; + ctx = qc->xprt_ctx; + } +#endif + + BUG_ON(!ctx || !bind_conf); + ALREADY_CHECKED(ctx); + ALREADY_CHECKED(bind_conf); + + ctx->xprt_st |= SSL_SOCK_ST_FL_VERIFY_DONE; + + depth = X509_STORE_CTX_get_error_depth(x_store); + err = X509_STORE_CTX_get_error(x_store); + + if (ok) /* no errors */ + return ok; + + /* Keep a reference to the client's certificate in order to be able to + * dump some fetches values in a log even when the verification process + * fails. */ + if (depth == 0) { + X509_free(client_crt); + client_crt = X509_STORE_CTX_get0_cert(x_store); + if (client_crt) { + X509_up_ref(client_crt); + SSL_set_ex_data(ssl, ssl_client_crt_ref_index, client_crt); + } + } + else { + /* An error occurred on a CA certificate of the certificate + * chain, we might never call this verify callback on the client + * certificate's depth (which is 0) so we try to store the + * reference right now. */ + certs = X509_STORE_CTX_get1_chain(x_store); + if (certs) { + client_crt = sk_X509_value(certs, 0); + if (client_crt) { + X509_up_ref(client_crt); + SSL_set_ex_data(ssl, ssl_client_crt_ref_index, client_crt); + } + sk_X509_pop_free(certs, X509_free); + } + } + + /* check if CA error needs to be ignored */ + if (depth > 0) { + if (!SSL_SOCK_ST_TO_CA_ERROR(ctx->xprt_st)) { + ctx->xprt_st |= SSL_SOCK_CA_ERROR_TO_ST(err); + ctx->xprt_st |= SSL_SOCK_CAEDEPTH_TO_ST(depth); + } + + if (err <= SSL_MAX_VFY_ERROR_CODE && + cert_ignerr_bitfield_get(bind_conf->ca_ignerr_bitfield, err)) + goto err_ignored; + + /* TODO: for QUIC connection, this error code is lost */ + if (conn) + conn->err_code = CO_ER_SSL_CA_FAIL; + return 0; + } + + if (!SSL_SOCK_ST_TO_CRTERROR(ctx->xprt_st)) + ctx->xprt_st |= SSL_SOCK_CRTERROR_TO_ST(err); + + /* check if certificate error needs to be ignored */ + if (err <= SSL_MAX_VFY_ERROR_CODE && + cert_ignerr_bitfield_get(bind_conf->crt_ignerr_bitfield, err)) + goto err_ignored; + + /* TODO: for QUIC connection, this error code is lost */ + if (conn) + conn->err_code = CO_ER_SSL_CRT_FAIL; + return 0; + + err_ignored: + ssl_sock_dump_errors(conn, qc); + ERR_clear_error(); + return 1; +} + +#ifdef TLS1_RT_HEARTBEAT +static void ssl_sock_parse_heartbeat(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl) +{ + /* test heartbeat received (write_p is set to 0 + for a received record) */ + if ((content_type == TLS1_RT_HEARTBEAT) && (write_p == 0)) { + struct ssl_sock_ctx *ctx = __conn_get_ssl_sock_ctx(conn); + const unsigned char *p = buf; + unsigned int payload; + + ctx->xprt_st |= SSL_SOCK_RECV_HEARTBEAT; + + /* Check if this is a CVE-2014-0160 exploitation attempt. */ + if (*p != TLS1_HB_REQUEST) + return; + + if (len < 1 + 2 + 16) /* 1 type + 2 size + 0 payload + 16 padding */ + goto kill_it; + + payload = (p[1] * 256) + p[2]; + if (3 + payload + 16 <= len) + return; /* OK no problem */ + kill_it: + /* We have a clear heartbleed attack (CVE-2014-0160), the + * advertised payload is larger than the advertised packet + * length, so we have garbage in the buffer between the + * payload and the end of the buffer (p+len). We can't know + * if the SSL stack is patched, and we don't know if we can + * safely wipe out the area between p+3+len and payload. + * So instead, we prevent the response from being sent by + * setting the max_send_fragment to 0 and we report an SSL + * error, which will kill this connection. It will be reported + * above as SSL_ERROR_SSL while an other handshake failure with + * a heartbeat message will be reported as SSL_ERROR_SYSCALL. + */ + ssl->max_send_fragment = 0; + SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_SSL_HANDSHAKE_FAILURE); + } +} +#endif + +static void ssl_sock_parse_clienthello(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl) +{ + struct ssl_capture *capture; + uchar *msg; + uchar *end; + uchar *extensions_end; + uchar *ec_start = NULL; + uchar *ec_formats_start = NULL; + uchar *list_end; + ushort protocol_version; + ushort extension_id; + ushort ec_len = 0; + uchar ec_formats_len = 0; + int offset = 0; + int rec_len; + + /* This function is called for "from client" and "to server" + * connections. The combination of write_p == 0 and content_type == 22 + * is only available during "from client" connection. + */ + + /* "write_p" is set to 0 is the bytes are received messages, + * otherwise it is set to 1. + */ + if (write_p != 0) + return; + + /* content_type contains the type of message received or sent + * according with the SSL/TLS protocol spec. This message is + * encoded with one byte. The value 256 (two bytes) is used + * for designing the SSL/TLS record layer. According with the + * rfc6101, the expected message (other than 256) are: + * - change_cipher_spec(20) + * - alert(21) + * - handshake(22) + * - application_data(23) + * - (255) + * We are interessed by the handshake and specially the client + * hello. + */ + if (content_type != 22) + return; + + /* The message length is at least 4 bytes, containing the + * message type and the message length. + */ + if (len < 4) + return; + + /* First byte of the handshake message id the type of + * message. The known types are: + * - hello_request(0) + * - client_hello(1) + * - server_hello(2) + * - certificate(11) + * - server_key_exchange (12) + * - certificate_request(13) + * - server_hello_done(14) + * We are interested by the client hello. + */ + msg = (unsigned char *)buf; + if (msg[0] != 1) + return; + + /* Next three bytes are the length of the message. The total length + * must be this decoded length + 4. If the length given as argument + * is not the same, we abort the protocol dissector. + */ + rec_len = (msg[1] << 16) + (msg[2] << 8) + msg[3]; + if (len < rec_len + 4) + return; + msg += 4; + end = msg + rec_len; + if (end < msg) + return; + + /* Expect 2 bytes for protocol version + * (1 byte for major and 1 byte for minor) + */ + if (msg + 2 > end) + return; + protocol_version = (msg[0] << 8) + msg[1]; + msg += 2; + + /* Expect the random, composed by 4 bytes for the unix time and + * 28 bytes for unix payload. So we jump 4 + 28. + */ + msg += 4 + 28; + if (msg > end) + return; + + /* Next, is session id: + * if present, we have to jump by length + 1 for the size information + * if not present, we have to jump by 1 only + */ + if (msg[0] > 0) + msg += msg[0]; + msg += 1; + if (msg > end) + return; + + /* Next two bytes are the ciphersuite length. */ + if (msg + 2 > end) + return; + rec_len = (msg[0] << 8) + msg[1]; + msg += 2; + if (msg + rec_len > end || msg + rec_len < msg) + return; + + capture = pool_zalloc(pool_head_ssl_capture); + if (!capture) + return; + /* Compute the xxh64 of the ciphersuite. */ + capture->xxh64 = XXH64(msg, rec_len, 0); + + /* Capture the ciphersuite. */ + capture->ciphersuite_len = MIN(global_ssl.capture_buffer_size, rec_len); + capture->ciphersuite_offset = 0; + memcpy(capture->data, msg, capture->ciphersuite_len); + msg += rec_len; + offset += capture->ciphersuite_len; + + /* Initialize other data */ + capture->protocol_version = protocol_version; + + /* Next, compression methods: + * if present, we have to jump by length + 1 for the size information + * if not present, we have to jump by 1 only + */ + if (msg[0] > 0) + msg += msg[0]; + msg += 1; + if (msg > end) + goto store_capture; + + /* We reached extensions */ + if (msg + 2 > end) + goto store_capture; + rec_len = (msg[0] << 8) + msg[1]; + msg += 2; + if (msg + rec_len > end || msg + rec_len < msg) + goto store_capture; + extensions_end = msg + rec_len; + capture->extensions_offset = offset; + + /* Parse each extension */ + while (msg + 4 < extensions_end) { + /* Add 2 bytes of extension_id */ + if (global_ssl.capture_buffer_size >= offset + 2) { + capture->data[offset++] = msg[0]; + capture->data[offset++] = msg[1]; + capture->extensions_len += 2; + } + else + break; + extension_id = (msg[0] << 8) + msg[1]; + /* Length of the extension */ + rec_len = (msg[2] << 8) + msg[3]; + + /* Expect 2 bytes extension id + 2 bytes extension size */ + msg += 2 + 2; + if (msg + rec_len > extensions_end || msg + rec_len < msg) + goto store_capture; + /* TLS Extensions + * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ + if (extension_id == 0x000a) { + /* Elliptic Curves: + * https://www.rfc-editor.org/rfc/rfc8422.html + * https://www.rfc-editor.org/rfc/rfc7919.html */ + list_end = msg + rec_len; + if (msg + 2 > list_end) + goto store_capture; + rec_len = (msg[0] << 8) + msg[1]; + msg += 2; + + if (msg + rec_len > list_end || msg + rec_len < msg) + goto store_capture; + /* Store location/size of the list */ + ec_start = msg; + ec_len = rec_len; + } + else if (extension_id == 0x000b) { + /* Elliptic Curves Point Formats: + * https://www.rfc-editor.org/rfc/rfc8422.html */ + list_end = msg + rec_len; + if (msg + 1 > list_end) + goto store_capture; + rec_len = msg[0]; + msg += 1; + + if (msg + rec_len > list_end || msg + rec_len < msg) + goto store_capture; + /* Store location/size of the list */ + ec_formats_start = msg; + ec_formats_len = rec_len; + } + msg += rec_len; + } + + if (ec_start) { + rec_len = ec_len; + if (offset + rec_len > global_ssl.capture_buffer_size) + rec_len = global_ssl.capture_buffer_size - offset; + memcpy(capture->data + offset, ec_start, rec_len); + capture->ec_offset = offset; + capture->ec_len = rec_len; + offset += rec_len; + } + if (ec_formats_start) { + rec_len = ec_formats_len; + if (offset + rec_len > global_ssl.capture_buffer_size) + rec_len = global_ssl.capture_buffer_size - offset; + memcpy(capture->data + offset, ec_formats_start, rec_len); + capture->ec_formats_offset = offset; + capture->ec_formats_len = rec_len; + offset += rec_len; + } + + store_capture: + SSL_set_ex_data(ssl, ssl_capture_ptr_index, capture); +} + + +#ifdef HAVE_SSL_KEYLOG +static void ssl_init_keylog(struct connection *conn, int write_p, int version, + int content_type, const void *buf, size_t len, + SSL *ssl) +{ + struct ssl_keylog *keylog; + + if (SSL_get_ex_data(ssl, ssl_keylog_index)) + return; + + keylog = pool_zalloc(pool_head_ssl_keylog); + if (!keylog) + return; + + if (!SSL_set_ex_data(ssl, ssl_keylog_index, keylog)) { + pool_free(pool_head_ssl_keylog, keylog); + return; + } +} +#endif + +/* Callback is called for ssl protocol analyse */ +void ssl_sock_msgcbk(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) +{ + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); + struct ssl_sock_msg_callback *cbk; + + /* Try to call all callback functions that were registered by using + * ssl_sock_register_msg_callback(). + */ + list_for_each_entry(cbk, &ssl_sock_msg_callbacks, list) { + cbk->func(conn, write_p, version, content_type, buf, len, ssl); + } +} + +#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) +static int ssl_sock_srv_select_protos(SSL *s, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) +{ + struct server *srv = arg; + + if (SSL_select_next_proto(out, outlen, in, inlen, (unsigned char *)srv->ssl_ctx.npn_str, + srv->ssl_ctx.npn_len) == OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_OK; + return SSL_TLSEXT_ERR_NOACK; +} +#endif + +#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) +/* This callback is used so that the server advertises the list of + * negotiable protocols for NPN. + */ +static int ssl_sock_advertise_npn_protos(SSL *s, const unsigned char **data, + unsigned int *len, void *arg) +{ + struct ssl_bind_conf *conf = arg; + + *data = (const unsigned char *)conf->npn_str; + *len = conf->npn_len; + return SSL_TLSEXT_ERR_OK; +} +#endif + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +/* This callback is used so that the server advertises the list of + * negotiable protocols for ALPN. + */ +static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out, + unsigned char *outlen, + const unsigned char *server, + unsigned int server_len, void *arg) +{ + struct ssl_bind_conf *conf = arg; +#ifdef USE_QUIC + struct quic_conn *qc = SSL_get_ex_data(s, ssl_qc_app_data_index); +#endif + + if (SSL_select_next_proto((unsigned char**) out, outlen, (const unsigned char *)conf->alpn_str, + conf->alpn_len, server, server_len) != OPENSSL_NPN_NEGOTIATED) { +#ifdef USE_QUIC + if (qc) + quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL); +#endif + return SSL_TLSEXT_ERR_NOACK; + } + +#ifdef USE_QUIC + if (qc && !quic_set_app_ops(qc, *out, *outlen)) { + quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL); + return SSL_TLSEXT_ERR_NOACK; + } +#endif + + return SSL_TLSEXT_ERR_OK; +} +#endif + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME +#ifndef SSL_NO_GENERATE_CERTIFICATES + +/* Configure a DNS SAN extension on a certificate. */ +int ssl_sock_add_san_ext(X509V3_CTX* ctx, X509* cert, const char *servername) { + int failure = 0; + X509_EXTENSION *san_ext = NULL; + CONF *conf = NULL; + struct buffer *san_name = get_trash_chunk(); + + conf = NCONF_new(NULL); + if (!conf) { + failure = 1; + goto cleanup; + } + + /* Build an extension based on the DNS entry above */ + chunk_appendf(san_name, "DNS:%s", servername); + san_ext = X509V3_EXT_nconf_nid(conf, ctx, NID_subject_alt_name, san_name->area); + if (!san_ext) { + failure = 1; + goto cleanup; + } + + /* Add the extension */ + if (!X509_add_ext(cert, san_ext, -1 /* Add to end */)) { + failure = 1; + goto cleanup; + } + + /* Success */ + failure = 0; + +cleanup: + if (NULL != san_ext) X509_EXTENSION_free(san_ext); + if (NULL != conf) NCONF_free(conf); + + return failure; +} + +/* Create a X509 certificate with the specified servername and serial. This + * function returns a SSL_CTX object or NULL if an error occurs. */ +static SSL_CTX * +ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl) +{ + X509 *cacert = bind_conf->ca_sign_ckch->cert; + EVP_PKEY *capkey = bind_conf->ca_sign_ckch->key; + SSL_CTX *ssl_ctx = NULL; + X509 *newcrt = NULL; + EVP_PKEY *pkey = NULL; + SSL *tmp_ssl = NULL; + CONF *ctmp = NULL; + X509_NAME *name; + const EVP_MD *digest; + X509V3_CTX ctx; + unsigned int i; + int key_type; + + /* Get the private key of the default certificate and use it */ +#ifdef HAVE_SSL_CTX_get0_privatekey + pkey = SSL_CTX_get0_privatekey(bind_conf->default_ctx); +#else + tmp_ssl = SSL_new(bind_conf->default_ctx); + if (tmp_ssl) + pkey = SSL_get_privatekey(tmp_ssl); +#endif + if (!pkey) + goto mkcert_error; + + /* Create the certificate */ + if (!(newcrt = X509_new())) + goto mkcert_error; + + /* Set version number for the certificate (X509v3) and the serial + * number */ + if (X509_set_version(newcrt, 2L) != 1) + goto mkcert_error; + ASN1_INTEGER_set(X509_get_serialNumber(newcrt), _HA_ATOMIC_ADD_FETCH(&ssl_ctx_serial, 1)); + + /* Set duration for the certificate */ + if (!X509_gmtime_adj(X509_getm_notBefore(newcrt), (long)-60*60*24) || + !X509_gmtime_adj(X509_getm_notAfter(newcrt),(long)60*60*24*365)) + goto mkcert_error; + + /* set public key in the certificate */ + if (X509_set_pubkey(newcrt, pkey) != 1) + goto mkcert_error; + + /* Set issuer name from the CA */ + if (!(name = X509_get_subject_name(cacert))) + goto mkcert_error; + if (X509_set_issuer_name(newcrt, name) != 1) + goto mkcert_error; + + /* Set the subject name using the same, but the CN */ + name = X509_NAME_dup(name); + if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char *)servername, + -1, -1, 0) != 1) { + X509_NAME_free(name); + goto mkcert_error; + } + if (X509_set_subject_name(newcrt, name) != 1) { + X509_NAME_free(name); + goto mkcert_error; + } + X509_NAME_free(name); + + /* Add x509v3 extensions as specified */ + ctmp = NCONF_new(NULL); + X509V3_set_ctx(&ctx, cacert, newcrt, NULL, NULL, 0); + for (i = 0; i < X509V3_EXT_SIZE; i++) { + X509_EXTENSION *ext; + + if (!(ext = X509V3_EXT_nconf(ctmp, &ctx, x509v3_ext_names[i], x509v3_ext_values[i]))) + goto mkcert_error; + if (!X509_add_ext(newcrt, ext, -1)) { + X509_EXTENSION_free(ext); + goto mkcert_error; + } + X509_EXTENSION_free(ext); + } + + /* Add SAN extension */ + if (ssl_sock_add_san_ext(&ctx, newcrt, servername)) { + goto mkcert_error; + } + + /* Sign the certificate with the CA private key */ + + key_type = EVP_PKEY_base_id(capkey); + + if (key_type == EVP_PKEY_DSA) + digest = EVP_sha1(); + else if (key_type == EVP_PKEY_RSA) + digest = EVP_sha256(); + else if (key_type == EVP_PKEY_EC) + digest = EVP_sha256(); + else { +#ifdef ASN1_PKEY_CTRL_DEFAULT_MD_NID + int nid; + + if (EVP_PKEY_get_default_digest_nid(capkey, &nid) <= 0) + goto mkcert_error; + if (!(digest = EVP_get_digestbynid(nid))) + goto mkcert_error; +#else + goto mkcert_error; +#endif + } + + if (!(X509_sign(newcrt, capkey, digest))) + goto mkcert_error; + + /* Create and set the new SSL_CTX */ + if (!(ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) + goto mkcert_error; + if (!SSL_CTX_use_PrivateKey(ssl_ctx, pkey)) + goto mkcert_error; + if (!SSL_CTX_use_certificate(ssl_ctx, newcrt)) + goto mkcert_error; + if (!SSL_CTX_check_private_key(ssl_ctx)) + goto mkcert_error; + + /* Build chaining the CA cert and the rest of the chain, keep these order */ +#if defined(SSL_CTX_add1_chain_cert) + if (!SSL_CTX_add1_chain_cert(ssl_ctx, bind_conf->ca_sign_ckch->cert)) { + goto mkcert_error; + } + + if (bind_conf->ca_sign_ckch->chain) { + for (i = 0; i < sk_X509_num(bind_conf->ca_sign_ckch->chain); i++) { + X509 *chain_cert = sk_X509_value(bind_conf->ca_sign_ckch->chain, i); + if (!SSL_CTX_add1_chain_cert(ssl_ctx, chain_cert)) { + goto mkcert_error; + } + } + } +#endif + + if (newcrt) X509_free(newcrt); + +#ifndef OPENSSL_NO_DH +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) + SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_get_tmp_dh_cbk); +#else + ssl_sock_set_tmp_dh_from_pkey(ssl_ctx, pkey); +#endif +#endif + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) +#if defined(SSL_CTX_set1_curves_list) + { + const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); + if (!SSL_CTX_set1_curves_list(ssl_ctx, ecdhe)) + goto end; + } +#endif +#else +#if defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) + { + const char *ecdhe = (bind_conf->ssl_conf.ecdhe ? bind_conf->ssl_conf.ecdhe : ECDHE_DEFAULT_CURVE); + EC_KEY *ecc; + int nid; + + if ((nid = OBJ_sn2nid(ecdhe)) == NID_undef) + goto end; + if (!(ecc = EC_KEY_new_by_curve_name(nid))) + goto end; + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecc); + EC_KEY_free(ecc); + } +#endif /* defined(SSL_CTX_set_tmp_ecdh) && !defined(OPENSSL_NO_ECDH) */ +#endif /* HA_OPENSSL_VERSION_NUMBER >= 0x10101000L */ + end: + return ssl_ctx; + + mkcert_error: + if (ctmp) NCONF_free(ctmp); + if (tmp_ssl) SSL_free(tmp_ssl); + if (ssl_ctx) SSL_CTX_free(ssl_ctx); + if (newcrt) X509_free(newcrt); + return NULL; +} + + +/* Do a lookup for a certificate in the LRU cache used to store generated + * certificates and immediately assign it to the SSL session if not null. */ +SSL_CTX * +ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl) +{ + struct lru64 *lru = NULL; + + if (ssl_ctx_lru_tree) { + HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_ckch->cert, 0); + if (lru && lru->domain) { + if (ssl) + SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return (SSL_CTX *)lru->data; + } + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + } + return NULL; +} + +/* Same as but without SSL session. This + * function is not thread-safe, it should only be used to check if a certificate + * exists in the lru cache (with no warranty it will not be removed by another + * thread). It is kept for backward compatibility. */ +SSL_CTX * +ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf) +{ + return ssl_sock_assign_generated_cert(key, bind_conf, NULL); +} + +/* Set a certificate int the LRU cache used to store generated + * certificate. Return 0 on success, otherwise -1 */ +int +ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf *bind_conf) +{ + struct lru64 *lru = NULL; + + if (ssl_ctx_lru_tree) { + HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_ckch->cert, 0); + if (!lru) { + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return -1; + } + if (lru->domain && lru->data) + lru->free((SSL_CTX *)lru->data); + lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_ckch->cert, 0, (void (*)(void *))SSL_CTX_free); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return 0; + } + return -1; +} + +/* Compute the key of the certificate. */ +unsigned int +ssl_sock_generated_cert_key(const void *data, size_t len) +{ + return XXH32(data, len, ssl_ctx_lru_seed); +} + +/* Generate a cert and immediately assign it to the SSL session so that the cert's + * refcount is maintained regardless of the cert's presence in the LRU cache. + */ +static int +ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_conf, SSL *ssl) +{ + X509 *cacert = bind_conf->ca_sign_ckch->cert; + SSL_CTX *ssl_ctx = NULL; + struct lru64 *lru = NULL; + unsigned int key; + + key = ssl_sock_generated_cert_key(servername, strlen(servername)); + if (ssl_ctx_lru_tree) { + HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0); + if (lru && lru->domain) + ssl_ctx = (SSL_CTX *)lru->data; + if (!ssl_ctx && lru) { + ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); + lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); + } + SSL_set_SSL_CTX(ssl, ssl_ctx); + HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock); + return 1; + } + else { + ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); + SSL_set_SSL_CTX(ssl, ssl_ctx); + /* No LRU cache, this CTX will be released as soon as the session dies */ + SSL_CTX_free(ssl_ctx); + return 1; + } + return 0; +} +static int +ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl) +{ + unsigned int key; + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); + + if (conn_get_dst(conn)) { + key = ssl_sock_generated_cert_key(conn->dst, get_addr_len(conn->dst)); + if (ssl_sock_assign_generated_cert(key, bind_conf, ssl)) + return 1; + } + return 0; +} +#endif /* !defined SSL_NO_GENERATE_CERTIFICATES */ + +#if (HA_OPENSSL_VERSION_NUMBER < 0x1010000fL) + +static void ctx_set_SSLv3_func(SSL_CTX *ctx, set_context_func c) +{ +#if SSL_OP_NO_SSLv3 + c == SET_SERVER ? SSL_CTX_set_ssl_version(ctx, SSLv3_server_method()) + : SSL_CTX_set_ssl_version(ctx, SSLv3_client_method()); +#endif +} +static void ctx_set_TLSv10_func(SSL_CTX *ctx, set_context_func c) { + c == SET_SERVER ? SSL_CTX_set_ssl_version(ctx, TLSv1_server_method()) + : SSL_CTX_set_ssl_version(ctx, TLSv1_client_method()); +} +static void ctx_set_TLSv11_func(SSL_CTX *ctx, set_context_func c) { +#if SSL_OP_NO_TLSv1_1 + c == SET_SERVER ? SSL_CTX_set_ssl_version(ctx, TLSv1_1_server_method()) + : SSL_CTX_set_ssl_version(ctx, TLSv1_1_client_method()); +#endif +} +static void ctx_set_TLSv12_func(SSL_CTX *ctx, set_context_func c) { +#if SSL_OP_NO_TLSv1_2 + c == SET_SERVER ? SSL_CTX_set_ssl_version(ctx, TLSv1_2_server_method()) + : SSL_CTX_set_ssl_version(ctx, TLSv1_2_client_method()); +#endif +} +/* TLSv1.2 is the last supported version in this context. */ +static void ctx_set_TLSv13_func(SSL_CTX *ctx, set_context_func c) {} +/* Unusable in this context. */ +static void ssl_set_SSLv3_func(SSL *ssl, set_context_func c) {} +static void ssl_set_TLSv10_func(SSL *ssl, set_context_func c) {} +static void ssl_set_TLSv11_func(SSL *ssl, set_context_func c) {} +static void ssl_set_TLSv12_func(SSL *ssl, set_context_func c) {} +static void ssl_set_TLSv13_func(SSL *ssl, set_context_func c) {} +#else /* openssl >= 1.1.0 */ + +static void ctx_set_SSLv3_func(SSL_CTX *ctx, set_context_func c) { + c == SET_MAX ? SSL_CTX_set_max_proto_version(ctx, SSL3_VERSION) + : SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); +} +static void ssl_set_SSLv3_func(SSL *ssl, set_context_func c) { + c == SET_MAX ? SSL_set_max_proto_version(ssl, SSL3_VERSION) + : SSL_set_min_proto_version(ssl, SSL3_VERSION); +} +static void ctx_set_TLSv10_func(SSL_CTX *ctx, set_context_func c) { + c == SET_MAX ? SSL_CTX_set_max_proto_version(ctx, TLS1_VERSION) + : SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION); +} +static void ssl_set_TLSv10_func(SSL *ssl, set_context_func c) { + c == SET_MAX ? SSL_set_max_proto_version(ssl, TLS1_VERSION) + : SSL_set_min_proto_version(ssl, TLS1_VERSION); +} +static void ctx_set_TLSv11_func(SSL_CTX *ctx, set_context_func c) { + c == SET_MAX ? SSL_CTX_set_max_proto_version(ctx, TLS1_1_VERSION) + : SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION); +} +static void ssl_set_TLSv11_func(SSL *ssl, set_context_func c) { + c == SET_MAX ? SSL_set_max_proto_version(ssl, TLS1_1_VERSION) + : SSL_set_min_proto_version(ssl, TLS1_1_VERSION); +} +static void ctx_set_TLSv12_func(SSL_CTX *ctx, set_context_func c) { + c == SET_MAX ? SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION) + : SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); +} +static void ssl_set_TLSv12_func(SSL *ssl, set_context_func c) { + c == SET_MAX ? SSL_set_max_proto_version(ssl, TLS1_2_VERSION) + : SSL_set_min_proto_version(ssl, TLS1_2_VERSION); +} +static void ctx_set_TLSv13_func(SSL_CTX *ctx, set_context_func c) { +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) + c == SET_MAX ? SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) + : SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); +#endif +} +static void ssl_set_TLSv13_func(SSL *ssl, set_context_func c) { +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) + c == SET_MAX ? SSL_set_max_proto_version(ssl, TLS1_3_VERSION) + : SSL_set_min_proto_version(ssl, TLS1_3_VERSION); +#endif +} +#endif +static void ctx_set_None_func(SSL_CTX *ctx, set_context_func c) { } +static void ssl_set_None_func(SSL *ssl, set_context_func c) { } + +struct methodVersions methodVersions[] = { + {0, 0, ctx_set_None_func, ssl_set_None_func, "NONE"}, /* CONF_TLSV_NONE */ + {SSL_OP_NO_SSLv3, MC_SSL_O_NO_SSLV3, ctx_set_SSLv3_func, ssl_set_SSLv3_func, "SSLv3"}, /* CONF_SSLV3 */ + {SSL_OP_NO_TLSv1, MC_SSL_O_NO_TLSV10, ctx_set_TLSv10_func, ssl_set_TLSv10_func, "TLSv1.0"}, /* CONF_TLSV10 */ + {SSL_OP_NO_TLSv1_1, MC_SSL_O_NO_TLSV11, ctx_set_TLSv11_func, ssl_set_TLSv11_func, "TLSv1.1"}, /* CONF_TLSV11 */ + {SSL_OP_NO_TLSv1_2, MC_SSL_O_NO_TLSV12, ctx_set_TLSv12_func, ssl_set_TLSv12_func, "TLSv1.2"}, /* CONF_TLSV12 */ + {SSL_OP_NO_TLSv1_3, MC_SSL_O_NO_TLSV13, ctx_set_TLSv13_func, ssl_set_TLSv13_func, "TLSv1.3"}, /* CONF_TLSV13 */ +}; + +static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx) +{ + SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx), ssl_sock_bind_verifycbk); + SSL_set_client_CA_list(ssl, SSL_dup_CA_list(SSL_CTX_get_client_CA_list(ctx))); + SSL_set_SSL_CTX(ssl, ctx); +} + +/* + * Return the right sni_ctx for a and a chosen (must be in lowercase) + * RSA and ECDSA capabilities of the client can also be used. + * + * This function does a lookup in the bind_conf sni tree so the caller should lock its tree. + */ +static __maybe_unused struct sni_ctx *ssl_sock_chose_sni_ctx(struct bind_conf *s, const char *servername, + int have_rsa_sig, int have_ecdsa_sig) +{ + struct ebmb_node *node, *n, *node_ecdsa = NULL, *node_rsa = NULL, *node_anonymous = NULL; + const char *wildp = NULL; + int i; + + /* look for the first dot for wildcard search */ + for (i = 0; servername[i] != '\0'; i++) { + if (servername[i] == '.') { + wildp = &servername[i]; + break; + } + } + + /* Look for an ECDSA, RSA and DSA certificate, first in the single + * name and if not found in the wildcard */ + for (i = 0; i < 2; i++) { + if (i == 0) /* lookup in full qualified names */ + node = ebst_lookup(&s->sni_ctx, trash.area); + else if (i == 1 && wildp) /* lookup in wildcards names */ + node = ebst_lookup(&s->sni_w_ctx, wildp); + else + break; + + for (n = node; n; n = ebmb_next_dup(n)) { + + /* lookup a not neg filter */ + if (!container_of(n, struct sni_ctx, name)->neg) { + struct sni_ctx *sni, *sni_tmp; + int skip = 0; + + if (i == 1 && wildp) { /* wildcard */ + /* If this is a wildcard, look for an exclusion on the same crt-list line */ + sni = container_of(n, struct sni_ctx, name); + list_for_each_entry(sni_tmp, &sni->ckch_inst->sni_ctx, by_ckch_inst) { + if (sni_tmp->neg && (strcmp((const char *)sni_tmp->name.key, trash.area) == 0)) { + skip = 1; + break; + } + } + if (skip) + continue; + } + + switch(container_of(n, struct sni_ctx, name)->kinfo.sig) { + case TLSEXT_signature_ecdsa: + if (!node_ecdsa) + node_ecdsa = n; + break; + case TLSEXT_signature_rsa: + if (!node_rsa) + node_rsa = n; + break; + default: /* TLSEXT_signature_anonymous|dsa */ + if (!node_anonymous) + node_anonymous = n; + break; + } + } + } + } + /* Once the certificates are found, select them depending on what is + * supported in the client and by key_signature priority order: EDSA > + * RSA > DSA */ + if (have_ecdsa_sig && node_ecdsa) + node = node_ecdsa; + else if (have_rsa_sig && node_rsa) + node = node_rsa; + else if (node_anonymous) + node = node_anonymous; + else if (node_ecdsa) + node = node_ecdsa; /* no ecdsa signature case (< TLSv1.2) */ + else + node = node_rsa; /* no rsa signature case (far far away) */ + + if (node) + return container_of(node, struct sni_ctx, name); + + return NULL; +} + +#ifdef HAVE_SSL_CLIENT_HELLO_CB + +int ssl_sock_switchctx_err_cbk(SSL *ssl, int *al, void *priv) +{ + struct bind_conf *s = priv; + (void)al; /* shut gcc stupid warning */ + + if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) || (s->options & BC_O_GENERATE_CERTS)) + return SSL_TLSEXT_ERR_OK; + return SSL_TLSEXT_ERR_NOACK; +} + +#ifdef OPENSSL_IS_BORINGSSL +int ssl_sock_switchctx_cbk(const struct ssl_early_callback_ctx *ctx) +{ + SSL *ssl = ctx->ssl; +#else +int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) +{ +#endif + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); +#ifdef USE_QUIC + struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); +#endif /* USE_QUIC */ + struct bind_conf *s = NULL; + const uint8_t *extension_data; + size_t extension_len; + int has_rsa_sig = 0, has_ecdsa_sig = 0; + struct sni_ctx *sni_ctx; + const char *servername; + size_t servername_len; + int allow_early = 0; + int i; + + if (conn) + s = __objt_listener(conn->target)->bind_conf; +#ifdef USE_QUIC + else if (qc) + s = qc->li->bind_conf; +#endif /* USE_QUIC */ + + if (!s) { + /* must never happen */ + ABORT_NOW(); + return 0; + } + +#ifdef USE_QUIC + if (qc) { + /* Look for the QUIC transport parameters. */ +#ifdef OPENSSL_IS_BORINGSSL + if (!SSL_early_callback_ctx_extension_get(ctx, qc->tps_tls_ext, + &extension_data, &extension_len)) +#else + if (!SSL_client_hello_get0_ext(ssl, qc->tps_tls_ext, + &extension_data, &extension_len)) +#endif + { + /* This is not redundant. It we only return 0 without setting + * <*al>, this has as side effect to generate another TLS alert + * which would be set after calling quic_set_tls_alert(). + */ + *al = SSL_AD_MISSING_EXTENSION; + quic_set_tls_alert(qc, SSL_AD_MISSING_EXTENSION); + return 0; + } + + if (!quic_transport_params_store(qc, 0, extension_data, + extension_data + extension_len)) + goto abort; + + qc->flags |= QUIC_FL_CONN_TX_TP_RECEIVED; + } +#endif /* USE_QUIC */ + + if (s->ssl_conf.early_data) + allow_early = 1; +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_server_name, + &extension_data, &extension_len)) { +#else + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &extension_data, &extension_len)) { +#endif + /* + * The server_name extension was given too much extensibility when it + * was written, so parsing the normal case is a bit complex. + */ + size_t len; + if (extension_len <= 2) + goto abort; + /* Extract the length of the supplied list of names. */ + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len + 2 != extension_len) + goto abort; + /* + * The list in practice only has a single element, so we only consider + * the first one. + */ + if (len == 0 || *extension_data++ != TLSEXT_NAMETYPE_host_name) + goto abort; + extension_len = len - 1; + /* Now we can finally pull out the byte array with the actual hostname. */ + if (extension_len <= 2) + goto abort; + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len == 0 || len + 2 > extension_len || len > TLSEXT_MAXLEN_host_name + || memchr(extension_data, 0, len) != NULL) + goto abort; + servername = (char *)extension_data; + servername_len = len; + } else { +#if (!defined SSL_NO_GENERATE_CERTIFICATES) + if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) { + goto allow_early; + } +#endif + /* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */ + if (!s->strict_sni) { + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + goto abort; + } + + /* extract/check clientHello information */ +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) { +#else + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_signature_algorithms, &extension_data, &extension_len)) { +#endif + uint8_t sign; + size_t len; + if (extension_len < 2) + goto abort; + len = (*extension_data++) << 8; + len |= *extension_data++; + if (len + 2 != extension_len) + goto abort; + if (len % 2 != 0) + goto abort; + for (; len > 0; len -= 2) { + extension_data++; /* hash */ + sign = *extension_data++; + switch (sign) { + case TLSEXT_signature_rsa: + has_rsa_sig = 1; + break; + case TLSEXT_signature_ecdsa: + has_ecdsa_sig = 1; + break; + default: + continue; + } + if (has_ecdsa_sig && has_rsa_sig) + break; + } + } else { + /* without TLSEXT_TYPE_signature_algorithms extension (< TLSv1.2) */ + has_rsa_sig = 1; + } + if (has_ecdsa_sig) { /* in very rare case: has ecdsa sign but not a ECDSA cipher */ + const SSL_CIPHER *cipher; + uint32_t cipher_id; + size_t len; + const uint8_t *cipher_suites; + has_ecdsa_sig = 0; +#ifdef OPENSSL_IS_BORINGSSL + len = ctx->cipher_suites_len; + cipher_suites = ctx->cipher_suites; +#else + len = SSL_client_hello_get0_ciphers(ssl, &cipher_suites); +#endif + if (len % 2 != 0) + goto abort; + for (; len != 0; len -= 2, cipher_suites += 2) { +#ifdef OPENSSL_IS_BORINGSSL + uint16_t cipher_suite = (cipher_suites[0] << 8) | cipher_suites[1]; + cipher = SSL_get_cipher_by_value(cipher_suite); +#else + cipher = SSL_CIPHER_find(ssl, cipher_suites); +#endif + if (!cipher) + continue; + + cipher_id = SSL_CIPHER_get_id(cipher); + /* skip the SCSV "fake" signaling ciphersuites because they are NID_auth_any (RFC 7507) */ + if (cipher_id == SSL3_CK_SCSV || cipher_id == SSL3_CK_FALLBACK_SCSV) + continue; + + if (SSL_CIPHER_get_auth_nid(cipher) == NID_auth_ecdsa + || SSL_CIPHER_get_auth_nid(cipher) == NID_auth_any) { + has_ecdsa_sig = 1; + break; + } + } + } + + /* we need to transform this a NULL-ended string in lowecase */ + for (i = 0; i < trash.size && i < servername_len; i++) + trash.area[i] = tolower(servername[i]); + trash.area[i] = 0; + servername = trash.area; + + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + sni_ctx = ssl_sock_chose_sni_ctx(s, servername, has_rsa_sig, has_ecdsa_sig); + if (sni_ctx) { + /* switch ctx */ + struct ssl_bind_conf *conf = sni_ctx->conf; + ssl_sock_switchctx_set(ssl, sni_ctx->ctx); + if (conf) { + methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN); + methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX); + if (conf->early_data) + allow_early = 1; + } + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); +#if (!defined SSL_NO_GENERATE_CERTIFICATES) + if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(servername, s, ssl)) { + /* switch ctx done in ssl_sock_generate_certificate */ + goto allow_early; + } +#endif + if (!s->strict_sni) { + /* no certificate match, is the default_ctx */ + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + + /* We are about to raise an handshake error so the servername extension + * callback will never be called and the SNI will never be stored in the + * SSL context. In order for the ssl_fc_sni sample fetch to still work + * in such a case, we store the SNI ourselves as an ex_data information + * in the SSL context. + */ + { + char *client_sni = pool_alloc(ssl_sock_client_sni_pool); + if (client_sni) { + strncpy(client_sni, servername, TLSEXT_MAXLEN_host_name); + client_sni[TLSEXT_MAXLEN_host_name] = '\0'; + SSL_set_ex_data(ssl, ssl_client_sni_index, client_sni); + } + } + + /* other cases fallback on abort, if strict-sni is set but no node was found */ + + abort: + /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */ + if (conn) + conn->err_code = CO_ER_SSL_HANDSHAKE; +#ifdef OPENSSL_IS_BORINGSSL + return ssl_select_cert_error; +#else + *al = SSL_AD_UNRECOGNIZED_NAME; + return 0; +#endif + +allow_early: +#ifdef OPENSSL_IS_BORINGSSL + if (allow_early) + SSL_set_early_data_enabled(ssl, 1); +#else + if (!allow_early) + SSL_set_max_early_data(ssl, 0); +#endif + return 1; +} + +#else /* ! HAVE_SSL_CLIENT_HELLO_CB */ + +/* Sets the SSL ctx of to match the advertised server name. Returns a + * warning when no match is found, which implies the default (first) cert + * will keep being used. + */ +int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv) +{ + const char *servername; + const char *wildp = NULL; + struct ebmb_node *node, *n; + struct bind_conf *s = priv; +#ifdef USE_QUIC + const uint8_t *extension_data; + size_t extension_len; + struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); +#endif /* USE_QUIC */ + int i; + (void)al; /* shut gcc stupid warning */ + +#ifdef USE_QUIC + if (qc) { + + /* Look for the QUIC transport parameters. */ + SSL_get_peer_quic_transport_params(ssl, &extension_data, &extension_len); + if (extension_len == 0) { + /* This is not redundant. It we only return 0 without setting + * <*al>, this has as side effect to generate another TLS alert + * which would be set after calling quic_set_tls_alert(). + */ + *al = SSL_AD_MISSING_EXTENSION; + quic_set_tls_alert(qc, SSL_AD_MISSING_EXTENSION); + return SSL_TLSEXT_ERR_NOACK; + } + + if (!quic_transport_params_store(qc, 0, extension_data, + extension_data + extension_len)) + return SSL_TLSEXT_ERR_NOACK; + + qc->flags |= QUIC_FL_CONN_TX_TP_RECEIVED; + } +#endif /* USE_QUIC */ + + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { +#if (!defined SSL_NO_GENERATE_CERTIFICATES) + if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate_from_conn(s, ssl)) + return SSL_TLSEXT_ERR_OK; +#endif + if (s->strict_sni) + return SSL_TLSEXT_ERR_ALERT_FATAL; + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + return SSL_TLSEXT_ERR_NOACK; + } + + for (i = 0; i < trash.size; i++) { + if (!servername[i]) + break; + trash.area[i] = tolower((unsigned char)servername[i]); + if (!wildp && (trash.area[i] == '.')) + wildp = &trash.area[i]; + } + trash.area[i] = 0; + + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + node = NULL; + /* lookup in full qualified names */ + for (n = ebst_lookup(&s->sni_ctx, trash.area); n; n = ebmb_next_dup(n)) { + /* lookup a not neg filter */ + if (!container_of(n, struct sni_ctx, name)->neg) { + node = n; + break; + } + } + if (!node && wildp) { + /* lookup in wildcards names */ + for (n = ebst_lookup(&s->sni_w_ctx, wildp); n; n = ebmb_next_dup(n)) { + /* lookup a not neg filter */ + if (!container_of(n, struct sni_ctx, name)->neg) { + node = n; + break; + } + } + } + if (!node) { +#if (!defined SSL_NO_GENERATE_CERTIFICATES) + if (s->options & BC_O_GENERATE_CERTS && ssl_sock_generate_certificate(servername, s, ssl)) { + /* switch ctx done in ssl_sock_generate_certificate */ + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + return SSL_TLSEXT_ERR_OK; + } +#endif + if (s->strict_sni) { + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + return SSL_TLSEXT_ERR_OK; + } + + /* switch ctx */ + ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + return SSL_TLSEXT_ERR_OK; +} +#endif /* (!) OPENSSL_IS_BORINGSSL */ +#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ + +#if 0 && defined(USE_OPENSSL_WOLFSSL) +/* This implement the equivalent of the clientHello Callback but using the cert_cb. + * WolfSSL is able to extract the sigalgs and ciphers of the client byt using the API + * provided in https://github.com/wolfSSL/wolfssl/pull/6963 + * + * Not activated for now since the PR is not merged. + */ +static int ssl_sock_switchctx_wolfSSL_cbk(WOLFSSL* ssl, void* arg) +{ + struct bind_conf *s = arg; + int has_rsa_sig = 0, has_ecdsa_sig = 0; + const char *servername; + struct sni_ctx *sni_ctx; + int i; + + if (!s) { + /* must never happen */ + ABORT_NOW(); + return 0; + } + + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { + /* without SNI extension, is the default_ctx (need SSL_TLSEXT_ERR_NOACK) */ + if (!s->strict_sni) { + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + goto abort; + } + + /* extract sigalgs and ciphers */ + { + const byte* suites = NULL; + word16 suiteSz = 0; + const byte* hashSigAlgo = NULL; + word16 hashSigAlgoSz = 0; + word16 idx = 0; + + wolfSSL_get_client_suites_sigalgs(ssl, &suites, &suiteSz, &hashSigAlgo, &hashSigAlgoSz); + if (suites == NULL || suiteSz == 0 || hashSigAlgo == NULL || hashSigAlgoSz == 0) + return 0; + + if (SSL_version(ssl) != TLS1_3_VERSION) { + for (idx = 0; idx < suiteSz; idx += 2) { + WOLFSSL_CIPHERSUITE_INFO info; + info = wolfSSL_get_ciphersuite_info(suites[idx], suites[idx+1]); + if (info.rsaAuth) + has_rsa_sig = 1; + else if (info.eccAuth) + has_ecdsa_sig = 1; + } + } + + if (hashSigAlgoSz > 0) { + /* sigalgs extension takes precedence over ciphersuites */ + has_ecdsa_sig = 0; + has_rsa_sig = 0; + } + for (idx = 0; idx < hashSigAlgoSz; idx += 2) { + int hashAlgo; + int sigAlgo; + + wolfSSL_get_sigalg_info(hashSigAlgo[idx+0], hashSigAlgo[idx+1], &hashAlgo, &sigAlgo); + + if (sigAlgo == RSAk || sigAlgo == RSAPSSk) + has_rsa_sig = 1; + else if (sigAlgo == ECDSAk) + has_ecdsa_sig = 1; + } + } + + /* we need to transform this into a NULL-ended string in lowecase */ + for (i = 0; i < trash.size && servername[i] != '\0'; i++) + trash.area[i] = tolower(servername[i]); + trash.area[i] = 0; + servername = trash.area; + + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + sni_ctx = ssl_sock_chose_sni_ctx(s, servername, has_rsa_sig, has_ecdsa_sig); + if (sni_ctx) { + /* switch ctx */ + struct ssl_bind_conf *conf = sni_ctx->conf; + ssl_sock_switchctx_set(ssl, sni_ctx->ctx); + if (conf) { + methodVersions[conf->ssl_methods.min].ssl_set_version(ssl, SET_MIN); + methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX); + } + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + if (!s->strict_sni) { + /* no certificate match, is the default_ctx */ + HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); + ssl_sock_switchctx_set(ssl, s->default_ctx); + HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock); + goto allow_early; + } + + /* We are about to raise an handshake error so the servername extension + * callback will never be called and the SNI will never be stored in the + * SSL context. In order for the ssl_fc_sni sample fetch to still work + * in such a case, we store the SNI ourselves as an ex_data information + * in the SSL context. + */ + { + char *client_sni = pool_alloc(ssl_sock_client_sni_pool); + if (client_sni) { + strncpy(client_sni, servername, TLSEXT_MAXLEN_host_name); + client_sni[TLSEXT_MAXLEN_host_name] = '\0'; + SSL_set_ex_data(ssl, ssl_client_sni_index, client_sni); + } + } + + /* other cases fallback on abort, if strict-sni is set but no node was found */ + + abort: + /* abort handshake (was SSL_TLSEXT_ERR_ALERT_FATAL) */ + return 0; + +allow_early: + return 1; +} +#endif + +#ifndef OPENSSL_NO_DH + +static inline HASSL_DH *ssl_new_dh_fromdata(BIGNUM *p, BIGNUM *g) +{ +#if (HA_OPENSSL_VERSION_NUMBER >= 0x3000000fL) + OSSL_PARAM_BLD *tmpl = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + if ((tmpl = OSSL_PARAM_BLD_new()) == NULL + || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_P, p) + || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_G, g) + || (params = OSSL_PARAM_BLD_to_param(tmpl)) == NULL) { + goto end; + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); + if (ctx == NULL + || !EVP_PKEY_fromdata_init(ctx) + || !EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEY_PARAMETERS, params)) { + goto end; + } + +end: + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(tmpl); + BN_free(p); + BN_free(g); + return pkey; +#else + + HASSL_DH *dh = DH_new(); + + if (!dh) + return NULL; + + DH_set0_pqg(dh, p, NULL, g); + + return dh; +#endif +} + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) +static inline HASSL_DH *ssl_get_dh_by_nid(int nid) +{ +#if (HA_OPENSSL_VERSION_NUMBER >= 0x3000000fL) + OSSL_PARAM params[2]; + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); + const char *named_group = NULL; + + if (!pctx) + goto end; + + named_group = OBJ_nid2ln(nid); + + if (!named_group) + goto end; + + params[0] = OSSL_PARAM_construct_utf8_string("group", (char*)named_group, 0); + params[1] = OSSL_PARAM_construct_end(); + + if (EVP_PKEY_keygen_init(pctx) && EVP_PKEY_CTX_set_params(pctx, params)) + EVP_PKEY_generate(pctx, &pkey); + +end: + EVP_PKEY_CTX_free(pctx); + return pkey; +#else + + HASSL_DH *dh = NULL; + dh = DH_new_by_nid(nid); + return dh; +#endif +} +#endif + + +static HASSL_DH * ssl_get_dh_1024(void) +{ + static unsigned char dh1024_p[]={ + 0xFA,0xF9,0x2A,0x22,0x2A,0xA7,0x7F,0xE1,0x67,0x4E,0x53,0xF7, + 0x56,0x13,0xC3,0xB1,0xE3,0x29,0x6B,0x66,0x31,0x6A,0x7F,0xB3, + 0xC2,0x68,0x6B,0xCB,0x1D,0x57,0x39,0x1D,0x1F,0xFF,0x1C,0xC9, + 0xA6,0xA4,0x98,0x82,0x31,0x5D,0x25,0xFF,0x8A,0xE0,0x73,0x96, + 0x81,0xC8,0x83,0x79,0xC1,0x5A,0x04,0xF8,0x37,0x0D,0xA8,0x3D, + 0xAE,0x74,0xBC,0xDB,0xB6,0xA4,0x75,0xD9,0x71,0x8A,0xA0,0x17, + 0x9E,0x2D,0xC8,0xA8,0xDF,0x2C,0x5F,0x82,0x95,0xF8,0x92,0x9B, + 0xA7,0x33,0x5F,0x89,0x71,0xC8,0x2D,0x6B,0x18,0x86,0xC4,0x94, + 0x22,0xA5,0x52,0x8D,0xF6,0xF6,0xD2,0x37,0x92,0x0F,0xA5,0xCC, + 0xDB,0x7B,0x1D,0x3D,0xA1,0x31,0xB7,0x80,0x8F,0x0B,0x67,0x5E, + 0x36,0xA5,0x60,0x0C,0xF1,0x95,0x33,0x8B, + }; + static unsigned char dh1024_g[]={ + 0x02, + }; + + BIGNUM *p; + BIGNUM *g; + + HASSL_DH *dh = NULL; + + p = BN_bin2bn(dh1024_p, sizeof dh1024_p, NULL); + g = BN_bin2bn(dh1024_g, sizeof dh1024_g, NULL); + + if (p && g) + dh = ssl_new_dh_fromdata(p, g); + + return dh; +} + +static HASSL_DH *ssl_get_dh_2048(void) +{ +#if (HA_OPENSSL_VERSION_NUMBER < 0x10101000L) + static unsigned char dh2048_p[]={ + 0xEC,0x86,0xF8,0x70,0xA0,0x33,0x16,0xEC,0x05,0x1A,0x73,0x59, + 0xCD,0x1F,0x8B,0xF8,0x29,0xE4,0xD2,0xCF,0x52,0xDD,0xC2,0x24, + 0x8D,0xB5,0x38,0x9A,0xFB,0x5C,0xA4,0xE4,0xB2,0xDA,0xCE,0x66, + 0x50,0x74,0xA6,0x85,0x4D,0x4B,0x1D,0x30,0xB8,0x2B,0xF3,0x10, + 0xE9,0xA7,0x2D,0x05,0x71,0xE7,0x81,0xDF,0x8B,0x59,0x52,0x3B, + 0x5F,0x43,0x0B,0x68,0xF1,0xDB,0x07,0xBE,0x08,0x6B,0x1B,0x23, + 0xEE,0x4D,0xCC,0x9E,0x0E,0x43,0xA0,0x1E,0xDF,0x43,0x8C,0xEC, + 0xBE,0xBE,0x90,0xB4,0x51,0x54,0xB9,0x2F,0x7B,0x64,0x76,0x4E, + 0x5D,0xD4,0x2E,0xAE,0xC2,0x9E,0xAE,0x51,0x43,0x59,0xC7,0x77, + 0x9C,0x50,0x3C,0x0E,0xED,0x73,0x04,0x5F,0xF1,0x4C,0x76,0x2A, + 0xD8,0xF8,0xCF,0xFC,0x34,0x40,0xD1,0xB4,0x42,0x61,0x84,0x66, + 0x42,0x39,0x04,0xF8,0x68,0xB2,0x62,0xD7,0x55,0xED,0x1B,0x74, + 0x75,0x91,0xE0,0xC5,0x69,0xC1,0x31,0x5C,0xDB,0x7B,0x44,0x2E, + 0xCE,0x84,0x58,0x0D,0x1E,0x66,0x0C,0xC8,0x44,0x9E,0xFD,0x40, + 0x08,0x67,0x5D,0xFB,0xA7,0x76,0x8F,0x00,0x11,0x87,0xE9,0x93, + 0xF9,0x7D,0xC4,0xBC,0x74,0x55,0x20,0xD4,0x4A,0x41,0x2F,0x43, + 0x42,0x1A,0xC1,0xF2,0x97,0x17,0x49,0x27,0x37,0x6B,0x2F,0x88, + 0x7E,0x1C,0xA0,0xA1,0x89,0x92,0x27,0xD9,0x56,0x5A,0x71,0xC1, + 0x56,0x37,0x7E,0x3A,0x9D,0x05,0xE7,0xEE,0x5D,0x8F,0x82,0x17, + 0xBC,0xE9,0xC2,0x93,0x30,0x82,0xF9,0xF4,0xC9,0xAE,0x49,0xDB, + 0xD0,0x54,0xB4,0xD9,0x75,0x4D,0xFA,0x06,0xB8,0xD6,0x38,0x41, + 0xB7,0x1F,0x77,0xF3, + }; + static unsigned char dh2048_g[]={ + 0x02, + }; + + BIGNUM *p; + BIGNUM *g; + + HASSL_DH *dh = NULL; + + p = BN_bin2bn(dh2048_p, sizeof dh2048_p, NULL); + g = BN_bin2bn(dh2048_g, sizeof dh2048_g, NULL); + + if (p && g) + dh = ssl_new_dh_fromdata(p, g); + + return dh; +#else + return ssl_get_dh_by_nid(NID_ffdhe2048); +#endif +} + +static HASSL_DH *ssl_get_dh_4096(void) +{ +#if (HA_OPENSSL_VERSION_NUMBER < 0x10101000L) + static unsigned char dh4096_p[]={ + 0xDE,0x16,0x94,0xCD,0x99,0x58,0x07,0xF1,0xF7,0x32,0x96,0x11, + 0x04,0x82,0xD4,0x84,0x72,0x80,0x99,0x06,0xCA,0xF0,0xA3,0x68, + 0x07,0xCE,0x64,0x50,0xE7,0x74,0x45,0x20,0x80,0x5E,0x4D,0xAD, + 0xA5,0xB6,0xED,0xFA,0x80,0x6C,0x3B,0x35,0xC4,0x9A,0x14,0x6B, + 0x32,0xBB,0xFD,0x1F,0x17,0x8E,0xB7,0x1F,0xD6,0xFA,0x3F,0x7B, + 0xEE,0x16,0xA5,0x62,0x33,0x0D,0xED,0xBC,0x4E,0x58,0xE5,0x47, + 0x4D,0xE9,0xAB,0x8E,0x38,0xD3,0x6E,0x90,0x57,0xE3,0x22,0x15, + 0x33,0xBD,0xF6,0x43,0x45,0xB5,0x10,0x0A,0xBE,0x2C,0xB4,0x35, + 0xB8,0x53,0x8D,0xAD,0xFB,0xA7,0x1F,0x85,0x58,0x41,0x7A,0x79, + 0x20,0x68,0xB3,0xE1,0x3D,0x08,0x76,0xBF,0x86,0x0D,0x49,0xE3, + 0x82,0x71,0x8C,0xB4,0x8D,0x81,0x84,0xD4,0xE7,0xBE,0x91,0xDC, + 0x26,0x39,0x48,0x0F,0x35,0xC4,0xCA,0x65,0xE3,0x40,0x93,0x52, + 0x76,0x58,0x7D,0xDD,0x51,0x75,0xDC,0x69,0x61,0xBF,0x47,0x2C, + 0x16,0x68,0x2D,0xC9,0x29,0xD3,0xE6,0xC0,0x99,0x48,0xA0,0x9A, + 0xC8,0x78,0xC0,0x6D,0x81,0x67,0x12,0x61,0x3F,0x71,0xBA,0x41, + 0x1F,0x6C,0x89,0x44,0x03,0xBA,0x3B,0x39,0x60,0xAA,0x28,0x55, + 0x59,0xAE,0xB8,0xFA,0xCB,0x6F,0xA5,0x1A,0xF7,0x2B,0xDD,0x52, + 0x8A,0x8B,0xE2,0x71,0xA6,0x5E,0x7E,0xD8,0x2E,0x18,0xE0,0x66, + 0xDF,0xDD,0x22,0x21,0x99,0x52,0x73,0xA6,0x33,0x20,0x65,0x0E, + 0x53,0xE7,0x6B,0x9B,0xC5,0xA3,0x2F,0x97,0x65,0x76,0xD3,0x47, + 0x23,0x77,0x12,0xB6,0x11,0x7B,0x24,0xED,0xF1,0xEF,0xC0,0xE2, + 0xA3,0x7E,0x67,0x05,0x3E,0x96,0x4D,0x45,0xC2,0x18,0xD1,0x73, + 0x9E,0x07,0xF3,0x81,0x6E,0x52,0x63,0xF6,0x20,0x76,0xB9,0x13, + 0xD2,0x65,0x30,0x18,0x16,0x09,0x16,0x9E,0x8F,0xF1,0xD2,0x10, + 0x5A,0xD3,0xD4,0xAF,0x16,0x61,0xDA,0x55,0x2E,0x18,0x5E,0x14, + 0x08,0x54,0x2E,0x2A,0x25,0xA2,0x1A,0x9B,0x8B,0x32,0xA9,0xFD, + 0xC2,0x48,0x96,0xE1,0x80,0xCA,0xE9,0x22,0x17,0xBB,0xCE,0x3E, + 0x9E,0xED,0xC7,0xF1,0x1F,0xEC,0x17,0x21,0xDC,0x7B,0x82,0x48, + 0x8E,0xBB,0x4B,0x9D,0x5B,0x04,0x04,0xDA,0xDB,0x39,0xDF,0x01, + 0x40,0xC3,0xAA,0x26,0x23,0x89,0x75,0xC6,0x0B,0xD0,0xA2,0x60, + 0x6A,0xF1,0xCC,0x65,0x18,0x98,0x1B,0x52,0xD2,0x74,0x61,0xCC, + 0xBD,0x60,0xAE,0xA3,0xA0,0x66,0x6A,0x16,0x34,0x92,0x3F,0x41, + 0x40,0x31,0x29,0xC0,0x2C,0x63,0xB2,0x07,0x8D,0xEB,0x94,0xB8, + 0xE8,0x47,0x92,0x52,0x93,0x6A,0x1B,0x7E,0x1A,0x61,0xB3,0x1B, + 0xF0,0xD6,0x72,0x9B,0xF1,0xB0,0xAF,0xBF,0x3E,0x65,0xEF,0x23, + 0x1D,0x6F,0xFF,0x70,0xCD,0x8A,0x4C,0x8A,0xA0,0x72,0x9D,0xBE, + 0xD4,0xBB,0x24,0x47,0x4A,0x68,0xB5,0xF5,0xC6,0xD5,0x7A,0xCD, + 0xCA,0x06,0x41,0x07,0xAD,0xC2,0x1E,0xE6,0x54,0xA7,0xAD,0x03, + 0xD9,0x12,0xC1,0x9C,0x13,0xB1,0xC9,0x0A,0x43,0x8E,0x1E,0x08, + 0xCE,0x50,0x82,0x73,0x5F,0xA7,0x55,0x1D,0xD9,0x59,0xAC,0xB5, + 0xEA,0x02,0x7F,0x6C,0x5B,0x74,0x96,0x98,0x67,0x24,0xA3,0x0F, + 0x15,0xFC,0xA9,0x7D,0x3E,0x67,0xD1,0x70,0xF8,0x97,0xF3,0x67, + 0xC5,0x8C,0x88,0x44,0x08,0x02,0xC7,0x2B, + }; + static unsigned char dh4096_g[]={ + 0x02, + }; + + BIGNUM *p; + BIGNUM *g; + + HASSL_DH *dh = NULL; + + p = BN_bin2bn(dh4096_p, sizeof dh4096_p, NULL); + g = BN_bin2bn(dh4096_g, sizeof dh4096_g, NULL); + + if (p && g) + dh = ssl_new_dh_fromdata(p, g); + + return dh; +#else + return ssl_get_dh_by_nid(NID_ffdhe4096); +#endif +} + +static HASSL_DH *ssl_get_tmp_dh(EVP_PKEY *pkey) +{ + HASSL_DH *dh = NULL; + int type; + int keylen = 0; + + type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE; + + if (type == EVP_PKEY_EC) { + keylen = global_ssl.default_dh_param; + } + + /* The keylen supplied by OpenSSL can only be 512 or 1024. + See ssl3_send_server_key_exchange() in ssl/s3_srvr.c + */ + if (type == EVP_PKEY_RSA || type == EVP_PKEY_DSA) { + keylen = EVP_PKEY_bits(pkey); + } + + if (keylen > global_ssl.default_dh_param) { + keylen = global_ssl.default_dh_param; + } + + if (keylen >= 4096) { + if (!local_dh_4096) + local_dh_4096 = ssl_get_dh_4096(); + dh = local_dh_4096; + } + else if (keylen >= 2048) { + if (!local_dh_2048) + local_dh_2048 = ssl_get_dh_2048(); + dh = local_dh_2048; + } + else { + if (!local_dh_1024) + local_dh_1024 = ssl_get_dh_1024(); + dh = local_dh_1024; + } + + return dh; +} + +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) +/* Returns Diffie-Hellman parameters matching the private key length + but not exceeding global_ssl.default_dh_param */ +static HASSL_DH *ssl_get_tmp_dh_cbk(SSL *ssl, int export, int keylen) +{ + EVP_PKEY *pkey = SSL_get_privatekey(ssl); + + return ssl_get_tmp_dh(pkey); +} +#endif + +static int ssl_sock_set_tmp_dh(SSL_CTX *ctx, HASSL_DH *dh) +{ +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) + return SSL_CTX_set_tmp_dh(ctx, dh); +#else + int retval = 0; + HASSL_DH_up_ref(dh); + + retval = SSL_CTX_set0_tmp_dh_pkey(ctx, dh); + + if (!retval) + HASSL_DH_free(dh); + + return retval; +#endif +} + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x3000000fL) +static void ssl_sock_set_tmp_dh_from_pkey(SSL_CTX *ctx, EVP_PKEY *pkey) +{ + HASSL_DH *dh = NULL; + if (pkey && (dh = ssl_get_tmp_dh(pkey))) { + HASSL_DH_up_ref(dh); + if (!SSL_CTX_set0_tmp_dh_pkey(ctx, dh)) + HASSL_DH_free(dh); + } +} +#endif + +HASSL_DH *ssl_sock_get_dh_from_bio(BIO *bio) +{ +#if (HA_OPENSSL_VERSION_NUMBER >= 0x3000000fL) + HASSL_DH *dh = NULL; + OSSL_DECODER_CTX *dctx = NULL; + const char *format = "PEM"; + const char *keytype = "DH"; + + dctx = OSSL_DECODER_CTX_new_for_pkey(&dh, format, NULL, keytype, + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + NULL, NULL); + + if (dctx == NULL || OSSL_DECODER_CTX_get_num_decoders(dctx) == 0) + goto end; + + /* The DH parameters might not be the first section found in the PEM + * file so we need to iterate over all of them until we find the right + * one. + */ + while (!BIO_eof(bio) && !dh) + OSSL_DECODER_from_bio(dctx, bio); + +end: + OSSL_DECODER_CTX_free(dctx); + return dh; +#else + HASSL_DH *dh = NULL; + + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + + return dh; +#endif +} + +static HASSL_DH * ssl_sock_get_dh_from_file(const char *filename) +{ + HASSL_DH *dh = NULL; + BIO *in = BIO_new(BIO_s_file()); + + if (in == NULL) + goto end; + + if (BIO_read_filename(in, filename) <= 0) + goto end; + + dh = ssl_sock_get_dh_from_bio(in); + +end: + if (in) + BIO_free(in); + + ERR_clear_error(); + + return dh; +} + +int ssl_sock_load_global_dh_param_from_file(const char *filename) +{ + global_dh = ssl_sock_get_dh_from_file(filename); + + if (global_dh) { + return 0; + } + + return -1; +} +#endif + +/* This function allocates a sni_ctx and adds it to the ckch_inst */ +static int ckch_inst_add_cert_sni(SSL_CTX *ctx, struct ckch_inst *ckch_inst, + struct bind_conf *s, struct ssl_bind_conf *conf, + struct pkey_info kinfo, char *name, int order) +{ + struct sni_ctx *sc; + int wild = 0, neg = 0; + + if (*name == '!') { + neg = 1; + name++; + } + if (*name == '*') { + wild = 1; + name++; + } + /* !* filter is a nop */ + if (neg && wild) + return order; + if (*name) { + int j, len; + len = strlen(name); + for (j = 0; j < len && j < trash.size; j++) + trash.area[j] = tolower((unsigned char)name[j]); + if (j >= trash.size) + return -1; + trash.area[j] = 0; + + sc = malloc(sizeof(struct sni_ctx) + len + 1); + if (!sc) + return -1; + memcpy(sc->name.key, trash.area, len + 1); + SSL_CTX_up_ref(ctx); + sc->ctx = ctx; + sc->conf = conf; + sc->kinfo = kinfo; + sc->order = order++; + sc->neg = neg; + sc->wild = wild; + sc->name.node.leaf_p = NULL; + sc->ckch_inst = ckch_inst; + LIST_APPEND(&ckch_inst->sni_ctx, &sc->by_ckch_inst); + } + return order; +} + +/* + * Insert the sni_ctxs that are listed in the ckch_inst, in the bind_conf's sni_ctx tree + * This function can't return an error. + * + * *CAUTION*: The caller must lock the sni tree if called in multithreading mode + */ +void ssl_sock_load_cert_sni(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf) +{ + + struct sni_ctx *sc0, *sc0b, *sc1; + struct ebmb_node *node; + + list_for_each_entry_safe(sc0, sc0b, &ckch_inst->sni_ctx, by_ckch_inst) { + + /* ignore if sc0 was already inserted in a tree */ + if (sc0->name.node.leaf_p) + continue; + + /* Check for duplicates. */ + if (sc0->wild) + node = ebst_lookup(&bind_conf->sni_w_ctx, (char *)sc0->name.key); + else + node = ebst_lookup(&bind_conf->sni_ctx, (char *)sc0->name.key); + + for (; node; node = ebmb_next_dup(node)) { + sc1 = ebmb_entry(node, struct sni_ctx, name); + if (sc1->ctx == sc0->ctx && sc1->conf == sc0->conf + && sc1->neg == sc0->neg && sc1->wild == sc0->wild) { + /* it's a duplicate, we should remove and free it */ + LIST_DELETE(&sc0->by_ckch_inst); + SSL_CTX_free(sc0->ctx); + ha_free(&sc0); + break; + } + } + + /* if duplicate, ignore the insertion */ + if (!sc0) + continue; + + if (sc0->wild) + ebst_insert(&bind_conf->sni_w_ctx, &sc0->name); + else + ebst_insert(&bind_conf->sni_ctx, &sc0->name); + } + + /* replace the default_ctx if required with the instance's ctx. */ + if (ckch_inst->is_default) { + SSL_CTX_free(bind_conf->default_ctx); + SSL_CTX_up_ref(ckch_inst->ctx); + bind_conf->default_ctx = ckch_inst->ctx; + bind_conf->default_inst = ckch_inst; + } +} + +/* + * tree used to store the ckchs ordered by filename/bundle name + */ +struct eb_root ckchs_tree = EB_ROOT_UNIQUE; + +/* tree of crtlist (crt-list/directory) */ +struct eb_root crtlists_tree = EB_ROOT_UNIQUE; + +/* Loads Diffie-Hellman parameter from a ckchs to an SSL_CTX. + * If there is no DH parameter available in the ckchs, the global + * DH parameter is loaded into the SSL_CTX and if there is no + * DH parameter available in ckchs nor in global, the default + * DH parameters are applied on the SSL_CTX. + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if a reason of the error is availabine in err + * ERR_WARN if a warning is available into err + * The value 0 means there is no error nor warning and + * the operation succeed. + */ +#ifndef OPENSSL_NO_DH +static int ssl_sock_load_dh_params(SSL_CTX *ctx, const struct ckch_data *data, + const char *path, char **err) +{ + int ret = 0; + HASSL_DH *dh = NULL; + + if (data && data->dh) { + dh = data->dh; + if (!ssl_sock_set_tmp_dh(ctx, dh)) { + memprintf(err, "%sunable to load the DH parameter specified in '%s'", + err && *err ? *err : "", path); + memprintf(err, "%s, DH ciphers won't be available.\n", + err && *err ? *err : ""); + ret |= ERR_WARN; + goto end; + } + + if (ssl_dh_ptr_index >= 0) { + /* store a pointer to the DH params to avoid complaining about + ssl-default-dh-param not being set for this SSL_CTX */ + SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, dh); + } + } + else if (global_dh) { + if (!ssl_sock_set_tmp_dh(ctx, global_dh)) { + memprintf(err, "%sunable to use the global DH parameter for certificate '%s'", + err && *err ? *err : "", path); + memprintf(err, "%s, DH ciphers won't be available.\n", + err && *err ? *err : ""); + ret |= ERR_WARN; + goto end; + } + } + else { + /* Clear openssl global errors stack */ + ERR_clear_error(); + + /* We do not want DHE ciphers to be added to the cipher list + * unless there is an explicit global dh option in the conf. + */ + if (global_ssl.default_dh_param) { + if (global_ssl.default_dh_param <= 1024) { + /* we are limited to DH parameter of 1024 bits anyway */ + if (local_dh_1024 == NULL) + local_dh_1024 = ssl_get_dh_1024(); + + if (local_dh_1024 == NULL) { + memprintf(err, "%sunable to load default 1024 bits DH parameter for certificate '%s'.\n", + err && *err ? *err : "", path); + ret |= ERR_ALERT | ERR_FATAL; + goto end; + } + + if (!ssl_sock_set_tmp_dh(ctx, local_dh_1024)) { + memprintf(err, "%sunable to load default 1024 bits DH parameter for certificate '%s'.\n", + err && *err ? *err : "", path); + memprintf(err, "%s, DH ciphers won't be available.\n", + err && *err ? *err : ""); + ret |= ERR_WARN; + goto end; + } + } + else { +#if (HA_OPENSSL_VERSION_NUMBER < 0x3000000fL) + SSL_CTX_set_tmp_dh_callback(ctx, ssl_get_tmp_dh_cbk); +#else + ssl_sock_set_tmp_dh_from_pkey(ctx, data ? data->key : NULL); +#endif + } + } + } + +end: + ERR_clear_error(); + return ret; +} +#endif + + +/* Load a certificate chain into an SSL context. + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if the reason of the error is available in err + * ERR_WARN if a warning is available into err + * The caller is responsible of freeing the newly built or newly refcounted + * find_chain element. + * The value 0 means there is no error nor warning and + * the operation succeed. + */ +static int ssl_sock_load_cert_chain(const char *path, const struct ckch_data *data, + SSL_CTX *ctx, STACK_OF(X509) **find_chain, char **err) +{ + int errcode = 0; + int ret; + + ERR_clear_error(); + + if (find_chain == NULL) { + errcode |= ERR_FATAL; + goto end; + } + + if (!SSL_CTX_use_certificate(ctx, data->cert)) { + ret = ERR_get_error(); + memprintf(err, "%sunable to load SSL certificate into SSL Context '%s': %s.\n", + err && *err ? *err : "", path, ERR_reason_error_string(ret)); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } + + if (data->chain) { + *find_chain = X509_chain_up_ref(data->chain); + } else { + /* Find Certificate Chain in global */ + struct issuer_chain *issuer; + issuer = ssl_get0_issuer_chain(data->cert); + if (issuer) + *find_chain = X509_chain_up_ref(issuer->chain); + } + + if (!*find_chain) { + /* always put a null chain stack in the SSL_CTX so it does not + * try to build the chain from the verify store */ + *find_chain = sk_X509_new_null(); + } + + /* Load all certs in the data into the ctx_chain for the ssl_ctx */ +#ifdef SSL_CTX_set1_chain + if (!SSL_CTX_set1_chain(ctx, *find_chain)) { + ret = ERR_get_error(); + memprintf(err, "%sunable to load chain certificate into SSL Context '%s': %s.\n", + err && *err ? *err : "", path, ERR_reason_error_string(ret)); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } +#else + { /* legacy compat (< openssl 1.0.2) */ + X509 *ca; + while ((ca = sk_X509_shift(*find_chain))) + if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) { + memprintf(err, "%sunable to load chain certificate into SSL Context '%s'.\n", + err && *err ? *err : "", path); + X509_free(ca); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } + } +#endif + +#ifdef SSL_CTX_build_cert_chain + /* remove the Root CA from the SSL_CTX if the option is activated */ + if (global_ssl.skip_self_issued_ca) { + if (!SSL_CTX_build_cert_chain(ctx, SSL_BUILD_CHAIN_FLAG_NO_ROOT|SSL_BUILD_CHAIN_FLAG_UNTRUSTED|SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR)) { + memprintf(err, "%sunable to load chain certificate into SSL Context '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } + } +#endif + +end: + return errcode; +} + + +/* Loads the info in ckch into ctx + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if the reason of the error is available in err + * ERR_WARN if a warning is available into err + * The value 0 means there is no error nor warning and + * the operation succeed. + */ +static int ssl_sock_put_ckch_into_ctx(const char *path, struct ckch_data *data, SSL_CTX *ctx, char **err) +{ + int errcode = 0; + STACK_OF(X509) *find_chain = NULL; + + ERR_clear_error(); + + if (SSL_CTX_use_PrivateKey(ctx, data->key) <= 0) { + int ret; + + ret = ERR_get_error(); + memprintf(err, "%sunable to load SSL private key into SSL Context '%s': %s.\n", + err && *err ? *err : "", path, ERR_reason_error_string(ret)); + errcode |= ERR_ALERT | ERR_FATAL; + return errcode; + } + + /* Load certificate chain */ + errcode |= ssl_sock_load_cert_chain(path, data, ctx, &find_chain, err); + if (errcode & ERR_CODE) + goto end; + +#ifndef OPENSSL_NO_DH + /* store a NULL pointer to indicate we have not yet loaded + a custom DH param file */ + if (ssl_dh_ptr_index >= 0) { + SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL); + } + + errcode |= ssl_sock_load_dh_params(ctx, data, path, err); + if (errcode & ERR_CODE) { + memprintf(err, "%sunable to load DH parameters from file '%s'.\n", + err && *err ? *err : "", path); + goto end; + } +#endif + +#ifdef HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT + if (sctl_ex_index >= 0 && data->sctl) { + if (ssl_sock_load_sctl(ctx, data->sctl) < 0) { + memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } + } +#endif + +#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL) + /* Load OCSP Info into context + * If OCSP update mode is set to 'on', an entry will be created in the + * ocsp tree even if no ocsp_response was known during init, unless the + * frontend's conf disables ocsp update explicitly. + */ + if (ssl_sock_load_ocsp(path, ctx, data, find_chain) < 0) { + if (data->ocsp_response) + memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n", + err && *err ? *err : "", path); + else + memprintf(err, "%s '%s' has an OCSP auto-update set to 'on' but an error occurred (maybe the OCSP URI or the issuer could not be found)'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto end; + } +#endif + + end: + sk_X509_pop_free(find_chain, X509_free); + return errcode; +} + + +/* Loads the info of a ckch built out of a backend certificate into an SSL ctx + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if the reason of the error is available in err + * ERR_WARN if a warning is available into err + * The value 0 means there is no error nor warning and + * the operation succeed. + */ +static int ssl_sock_put_srv_ckch_into_ctx(const char *path, const struct ckch_data *data, + SSL_CTX *ctx, char **err) +{ + int errcode = 0; + STACK_OF(X509) *find_chain = NULL; + + /* Load the private key */ + if (SSL_CTX_use_PrivateKey(ctx, data->key) <= 0) { + memprintf(err, "%sunable to load SSL private key into SSL Context '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + } + + /* Load certificate chain */ + errcode |= ssl_sock_load_cert_chain(path, data, ctx, &find_chain, err); + if (errcode & ERR_CODE) + goto end; + + if (SSL_CTX_check_private_key(ctx) <= 0) { + memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + } + +end: + sk_X509_pop_free(find_chain, X509_free); + return errcode; +} + + +/* + * This function allocate a ckch_inst and create its snis + * + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if the reason of the error is available in err + * ERR_WARN if a warning is available into err + */ +int ckch_inst_new_load_store(const char *path, struct ckch_store *ckchs, struct bind_conf *bind_conf, + struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount, struct ckch_inst **ckchi, char **err) +{ + SSL_CTX *ctx; + int i; + int order = 0; + X509_NAME *xname; + char *str; + EVP_PKEY *pkey; + struct pkey_info kinfo = { .sig = TLSEXT_signature_anonymous, .bits = 0 }; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + STACK_OF(GENERAL_NAME) *names; +#endif + struct ckch_data *data; + struct ckch_inst *ckch_inst = NULL; + int errcode = 0; + + *ckchi = NULL; + + if (!ckchs || !ckchs->data) + return ERR_FATAL; + + data = ckchs->data; + + ctx = SSL_CTX_new(SSLv23_server_method()); + if (!ctx) { + memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + + errcode |= ssl_sock_put_ckch_into_ctx(path, data, ctx, err); + if (errcode & ERR_CODE) + goto error; + + ckch_inst = ckch_inst_new(); + if (!ckch_inst) { + memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + + pkey = X509_get_pubkey(data->cert); + if (pkey) { + kinfo.bits = EVP_PKEY_bits(pkey); + switch(EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + kinfo.sig = TLSEXT_signature_rsa; + break; + case EVP_PKEY_EC: + kinfo.sig = TLSEXT_signature_ecdsa; + break; + case EVP_PKEY_DSA: + kinfo.sig = TLSEXT_signature_dsa; + break; + } + EVP_PKEY_free(pkey); + } + + if (fcount) { + while (fcount--) { + order = ckch_inst_add_cert_sni(ctx, ckch_inst, bind_conf, ssl_conf, kinfo, sni_filter[fcount], order); + if (order < 0) { + memprintf(err, "%sunable to create a sni context.\n", err && *err ? *err : ""); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + } + } + else { +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + names = X509_get_ext_d2i(data->cert, NID_subject_alt_name, NULL, NULL); + if (names) { + for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { + GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); + if (name->type == GEN_DNS) { + if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) { + order = ckch_inst_add_cert_sni(ctx, ckch_inst, bind_conf, ssl_conf, kinfo, str, order); + OPENSSL_free(str); + if (order < 0) { + memprintf(err, "%sunable to create a sni context.\n", err && *err ? *err : ""); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + } + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + } +#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ + xname = X509_get_subject_name(data->cert); + i = -1; + while ((i = X509_NAME_get_index_by_NID(xname, NID_commonName, i)) != -1) { + X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, i); + ASN1_STRING *value; + + value = X509_NAME_ENTRY_get_data(entry); + if (ASN1_STRING_to_UTF8((unsigned char **)&str, value) >= 0) { + order = ckch_inst_add_cert_sni(ctx, ckch_inst, bind_conf, ssl_conf, kinfo, str, order); + OPENSSL_free(str); + if (order < 0) { + memprintf(err, "%sunable to create a sni context.\n", err && *err ? *err : ""); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + } + } + } + /* we must not free the SSL_CTX anymore below, since it's already in + * the tree, so it will be discovered and cleaned in time. + */ + +#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (bind_conf->default_ctx) { + memprintf(err, "%sthis version of openssl cannot load multiple SSL certificates.\n", + err && *err ? *err : ""); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } +#endif + if (!bind_conf->default_ctx) { + bind_conf->default_ctx = ctx; + bind_conf->default_ssl_conf = ssl_conf; + ckch_inst->is_default = 1; + SSL_CTX_up_ref(ctx); + bind_conf->default_inst = ckch_inst; + } + + /* Always keep a reference to the newly constructed SSL_CTX in the + * instance. This way if the instance has no SNIs, the SSL_CTX will + * still be linked. */ + SSL_CTX_up_ref(ctx); + ckch_inst->ctx = ctx; + + /* everything succeed, the ckch instance can be used */ + ckch_inst->bind_conf = bind_conf; + ckch_inst->ssl_conf = ssl_conf; + ckch_inst->ckch_store = ckchs; + + SSL_CTX_free(ctx); /* we need to free the ctx since we incremented the refcount where it's used */ + + *ckchi = ckch_inst; + return errcode; + +error: + /* free the allocated sni_ctxs */ + if (ckch_inst) { + if (ckch_inst->is_default) + SSL_CTX_free(ctx); + + ckch_inst_free(ckch_inst); + ckch_inst = NULL; + } + SSL_CTX_free(ctx); + + return errcode; +} + + +/* + * This function allocate a ckch_inst that will be used on the backend side + * (server line) + * + * Returns a bitfield containing the flags: + * ERR_FATAL in any fatal error case + * ERR_ALERT if the reason of the error is available in err + * ERR_WARN if a warning is available into err + */ +int ckch_inst_new_load_srv_store(const char *path, struct ckch_store *ckchs, + struct ckch_inst **ckchi, char **err) +{ + SSL_CTX *ctx; + struct ckch_data *data; + struct ckch_inst *ckch_inst = NULL; + int errcode = 0; + + *ckchi = NULL; + + if (!ckchs || !ckchs->data) + return ERR_FATAL; + + data = ckchs->data; + + ctx = SSL_CTX_new(SSLv23_client_method()); + if (!ctx) { + memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + + errcode |= ssl_sock_put_srv_ckch_into_ctx(path, data, ctx, err); + if (errcode & ERR_CODE) + goto error; + + ckch_inst = ckch_inst_new(); + if (!ckch_inst) { + memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n", + err && *err ? *err : "", path); + errcode |= ERR_ALERT | ERR_FATAL; + goto error; + } + + /* everything succeed, the ckch instance can be used */ + ckch_inst->bind_conf = NULL; + ckch_inst->ssl_conf = NULL; + ckch_inst->ckch_store = ckchs; + ckch_inst->ctx = ctx; + ckch_inst->is_server_instance = 1; + + *ckchi = ckch_inst; + return errcode; + +error: + SSL_CTX_free(ctx); + + return errcode; +} + +/* Returns a set of ERR_* flags possibly with an error in . */ +static int ssl_sock_load_ckchs(const char *path, struct ckch_store *ckchs, + struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf, + char **sni_filter, int fcount, struct ckch_inst **ckch_inst, char **err) +{ + int errcode = 0; + + /* we found the ckchs in the tree, we can use it directly */ + errcode |= ckch_inst_new_load_store(path, ckchs, bind_conf, ssl_conf, sni_filter, fcount, ckch_inst, err); + + if (errcode & ERR_CODE) + return errcode; + + ssl_sock_load_cert_sni(*ckch_inst, bind_conf); + + /* succeed, add the instance to the ckch_store's list of instance */ + LIST_APPEND(&ckchs->ckch_inst, &((*ckch_inst)->by_ckchs)); + return errcode; +} + +/* This function generates a for a , and + * fill the SSL_CTX of the server. + * + * Returns a set of ERR_* flags possibly with an error in . */ +static int ssl_sock_load_srv_ckchs(const char *path, struct ckch_store *ckchs, + struct server *server, struct ckch_inst **ckch_inst, char **err) +{ + int errcode = 0; + + /* we found the ckchs in the tree, we can use it directly */ + errcode |= ckch_inst_new_load_srv_store(path, ckchs, ckch_inst, err); + + if (errcode & ERR_CODE) + return errcode; + + (*ckch_inst)->server = server; + /* Keep the reference to the SSL_CTX in the server. */ + SSL_CTX_up_ref((*ckch_inst)->ctx); + server->ssl_ctx.ctx = (*ckch_inst)->ctx; + /* succeed, add the instance to the ckch_store's list of instance */ + LIST_APPEND(&ckchs->ckch_inst, &((*ckch_inst)->by_ckchs)); + return errcode; +} + + + + +/* Make sure openssl opens /dev/urandom before the chroot. The work is only + * done once. Zero is returned if the operation fails. No error is returned + * if the random is said as not implemented, because we expect that openssl + * will use another method once needed. + */ +int ssl_initialize_random(void) +{ + unsigned char random; + static int random_initialized = 0; + + if (!random_initialized && RAND_bytes(&random, 1) != 0) + random_initialized = 1; + + return random_initialized; +} + +/* Load a crt-list file, this is done in 2 parts: + * - store the content of the file in a crtlist structure with crtlist_entry structures + * - generate the instances by iterating on entries in the crtlist struct + * + * Nothing is locked there, this function is used in the configuration parser. + * + * Returns a set of ERR_* flags possibly with an error in . + */ +int ssl_sock_load_cert_list_file(char *file, int dir, struct bind_conf *bind_conf, struct proxy *curproxy, char **err) +{ + struct crtlist *crtlist = NULL; + struct ebmb_node *eb; + struct crtlist_entry *entry = NULL; + struct bind_conf_list *bind_conf_node = NULL; + int cfgerr = 0; + char *end; + + bind_conf_node = malloc(sizeof(*bind_conf_node)); + if (!bind_conf_node) { + memprintf(err, "%sCan't alloc memory!\n", err && *err ? *err : ""); + cfgerr |= ERR_FATAL | ERR_ALERT; + goto error; + } + bind_conf_node->next = NULL; + bind_conf_node->bind_conf = bind_conf; + + /* strip trailing slashes, including first one */ + for (end = file + strlen(file) - 1; end >= file && *end == '/'; end--) + *end = 0; + + /* look for an existing crtlist or create one */ + eb = ebst_lookup(&crtlists_tree, file); + if (eb) { + crtlist = ebmb_entry(eb, struct crtlist, node); + } else { + /* load a crt-list OR a directory */ + if (dir) + cfgerr |= crtlist_load_cert_dir(file, bind_conf, &crtlist, err); + else + cfgerr |= crtlist_parse_file(file, bind_conf, curproxy, &crtlist, err); + + if (!(cfgerr & ERR_CODE)) + ebst_insert(&crtlists_tree, &crtlist->node); + } + + if (cfgerr & ERR_CODE) { + cfgerr |= ERR_FATAL | ERR_ALERT; + goto error; + } + + /* generates ckch instance from the crtlist_entry */ + list_for_each_entry(entry, &crtlist->ord_entries, by_crtlist) { + struct ckch_store *store; + struct ckch_inst *ckch_inst = NULL; + + store = entry->node.key; + cfgerr |= ssl_sock_load_ckchs(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &ckch_inst, err); + if (cfgerr & ERR_CODE) { + memprintf(err, "error processing line %d in file '%s' : %s", entry->linenum, file, *err); + goto error; + } + LIST_APPEND(&entry->ckch_inst, &ckch_inst->by_crtlist_entry); + ckch_inst->crtlist_entry = entry; + } + + /* add the bind_conf to the list */ + bind_conf_node->next = crtlist->bind_conf; + crtlist->bind_conf = bind_conf_node; + + return cfgerr; +error: + { + struct crtlist_entry *lastentry; + struct ckch_inst *inst, *s_inst; + + lastentry = entry; /* which entry we tried to generate last */ + if (lastentry) { + list_for_each_entry(entry, &crtlist->ord_entries, by_crtlist) { + if (entry == lastentry) /* last entry we tried to generate, no need to go further */ + break; + + list_for_each_entry_safe(inst, s_inst, &entry->ckch_inst, by_crtlist_entry) { + + /* this was not generated for this bind_conf, skip */ + if (inst->bind_conf != bind_conf) + continue; + + /* free the sni_ctx and instance */ + ckch_inst_free(inst); + } + } + } + free(bind_conf_node); + } + return cfgerr; +} + +/* Returns a set of ERR_* flags possibly with an error in . */ +int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err) +{ + struct stat buf; + int cfgerr = 0; + struct ckch_store *ckchs; + struct ckch_inst *ckch_inst = NULL; + int found = 0; /* did we found a file to load ? */ + + if ((ckchs = ckchs_lookup(path))) { + /* we found the ckchs in the tree, we can use it directly */ + cfgerr |= ssl_sock_load_ckchs(path, ckchs, bind_conf, NULL, NULL, 0, &ckch_inst, err); + found++; + } else if (stat(path, &buf) == 0) { + found++; + if (S_ISDIR(buf.st_mode) == 0) { + ckchs = ckchs_load_cert_file(path, err); + if (!ckchs) + cfgerr |= ERR_ALERT | ERR_FATAL; + cfgerr |= ssl_sock_load_ckchs(path, ckchs, bind_conf, NULL, NULL, 0, &ckch_inst, err); + } else { + cfgerr |= ssl_sock_load_cert_list_file(path, 1, bind_conf, bind_conf->frontend, err); + } + } else { + /* stat failed, could be a bundle */ + if (global_ssl.extra_files & SSL_GF_BUNDLE) { + char fp[MAXPATHLEN+1] = {0}; + int n = 0; + + /* Load all possible certs and keys in separate ckch_store */ + for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) { + struct stat buf; + int ret; + + ret = snprintf(fp, sizeof(fp), "%s.%s", path, SSL_SOCK_KEYTYPE_NAMES[n]); + if (ret > sizeof(fp)) + continue; + + if ((ckchs = ckchs_lookup(fp))) { + cfgerr |= ssl_sock_load_ckchs(fp, ckchs, bind_conf, NULL, NULL, 0, &ckch_inst, err); + found++; + } else { + if (stat(fp, &buf) == 0) { + found++; + ckchs = ckchs_load_cert_file(fp, err); + if (!ckchs) + cfgerr |= ERR_ALERT | ERR_FATAL; + cfgerr |= ssl_sock_load_ckchs(fp, ckchs, bind_conf, NULL, NULL, 0, &ckch_inst, err); + } + } + } +#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L + if (found) { + memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n", + err && *err ? *err : "", path); + cfgerr |= ERR_ALERT | ERR_FATAL; + } +#endif + } + } + if (!found) { + memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", + err && *err ? *err : "", path, strerror(errno)); + cfgerr |= ERR_ALERT | ERR_FATAL; + } + + return cfgerr; +} + + +/* Create a full ssl context and ckch instance that will be used for a specific + * backend server (server configuration line). + * Returns a set of ERR_* flags possibly with an error in . + */ +int ssl_sock_load_srv_cert(char *path, struct server *server, int create_if_none, char **err) +{ + struct stat buf; + int cfgerr = 0; + struct ckch_store *ckchs; + int found = 0; /* did we found a file to load ? */ + + if ((ckchs = ckchs_lookup(path))) { + /* we found the ckchs in the tree, we can use it directly */ + cfgerr |= ssl_sock_load_srv_ckchs(path, ckchs, server, &server->ssl_ctx.inst, err); + found++; + } else { + if (!create_if_none) { + memprintf(err, "%sunable to stat SSL certificate '%s'.\n", + err && *err ? *err : "", path); + cfgerr |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (stat(path, &buf) == 0) { + /* We do not manage directories on backend side. */ + if (S_ISDIR(buf.st_mode) == 0) { + ++found; + ckchs = ckchs_load_cert_file(path, err); + if (!ckchs) + cfgerr |= ERR_ALERT | ERR_FATAL; + cfgerr |= ssl_sock_load_srv_ckchs(path, ckchs, server, &server->ssl_ctx.inst, err); + } + } + } + if (!found) { + memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n", + err && *err ? *err : "", path, strerror(errno)); + cfgerr |= ERR_ALERT | ERR_FATAL; + } + +out: + return cfgerr; +} + +/* Create an initial CTX used to start the SSL connection before switchctx */ +static int +ssl_sock_initial_ctx(struct bind_conf *bind_conf) +{ + SSL_CTX *ctx = NULL; + long options = + SSL_OP_ALL | /* all known workarounds for bugs */ + SSL_OP_NO_SSLv2 | + SSL_OP_NO_COMPRESSION | + SSL_OP_SINGLE_DH_USE | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_PRIORITIZE_CHACHA | + SSL_OP_CIPHER_SERVER_PREFERENCE; + long mode = + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | + SSL_MODE_RELEASE_BUFFERS | + SSL_MODE_SMALL_BUFFERS; + struct tls_version_filter *conf_ssl_methods = &bind_conf->ssl_conf.ssl_methods; + int i, min, max, hole; + int flags = MC_SSL_O_ALL; + int cfgerr = 0; + const int default_min_ver = CONF_TLSV12; + + ctx = SSL_CTX_new(SSLv23_server_method()); + bind_conf->initial_ctx = ctx; + + if (conf_ssl_methods->flags && (conf_ssl_methods->min || conf_ssl_methods->max)) + ha_warning("Proxy '%s': no-sslv3/no-tlsv1x are ignored for bind '%s' at [%s:%d]. " + "Use only 'ssl-min-ver' and 'ssl-max-ver' to fix.\n", + bind_conf->frontend->id, bind_conf->arg, bind_conf->file, bind_conf->line); + else + flags = conf_ssl_methods->flags; + + min = conf_ssl_methods->min; + max = conf_ssl_methods->max; + + /* default minimum is TLSV12, */ + if (!min) { + if (!max || (max >= default_min_ver)) { + min = default_min_ver; + } else { + ha_warning("Proxy '%s': Ambiguous configuration for bind '%s' at [%s:%d]: the ssl-min-ver value is not configured and the ssl-max-ver value is lower than the default ssl-min-ver value (%s). " + "Setting the ssl-min-ver to %s. Use 'ssl-min-ver' to fix this.\n", + bind_conf->frontend->id, bind_conf->arg, bind_conf->file, bind_conf->line, methodVersions[default_min_ver].name, methodVersions[max].name); + min = max; + } + } + /* Real min and max should be determinate with configuration and openssl's capabilities */ + if (min) + flags |= (methodVersions[min].flag - 1); + if (max) + flags |= ~((methodVersions[max].flag << 1) - 1); + /* find min, max and holes */ + min = max = CONF_TLSV_NONE; + hole = 0; + for (i = CONF_TLSV_MIN; i <= CONF_TLSV_MAX; i++) + /* version is in openssl && version not disable in configuration */ + if (methodVersions[i].option && !(flags & methodVersions[i].flag)) { + if (min) { + if (hole) { + ha_warning("Proxy '%s': SSL/TLS versions range not contiguous for bind '%s' at [%s:%d]. " + "Hole find for %s. Use only 'ssl-min-ver' and 'ssl-max-ver' to fix.\n", + bind_conf->frontend->id, bind_conf->arg, bind_conf->file, bind_conf->line, + methodVersions[hole].name); + hole = 0; + } + max = i; + } + else { + min = max = i; + } + } + else { + if (min) + hole = i; + } + if (!min) { + ha_alert("Proxy '%s': all SSL/TLS versions are disabled for bind '%s' at [%s:%d].\n", + bind_conf->frontend->id, bind_conf->arg, bind_conf->file, bind_conf->line); + cfgerr += 1; + } + /* save real min/max in bind_conf */ + conf_ssl_methods->min = min; + conf_ssl_methods->max = max; + +#if (HA_OPENSSL_VERSION_NUMBER < 0x1010000fL) + /* Keep force-xxx implementation as it is in older haproxy. It's a + precautionary measure to avoid any surprise with older openssl version. */ + if (min == max) + methodVersions[min].ctx_set_version(ctx, SET_SERVER); + else + for (i = CONF_TLSV_MIN; i <= CONF_TLSV_MAX; i++) { + /* clear every version flags in case SSL_CTX_new() + * returns an SSL_CTX with disabled versions */ + SSL_CTX_clear_options(ctx, methodVersions[i].option); + + if (flags & methodVersions[i].flag) + options |= methodVersions[i].option; + + } +#else /* openssl >= 1.1.0 */ + /* set the max_version is required to cap TLS version or activate new TLS (v1.3) */ + methodVersions[min].ctx_set_version(ctx, SET_MIN); + methodVersions[max].ctx_set_version(ctx, SET_MAX); +#endif + + if (bind_conf->ssl_options & BC_SSL_O_NO_TLS_TICKETS) + options |= SSL_OP_NO_TICKET; + if (bind_conf->ssl_options & BC_SSL_O_PREF_CLIE_CIPH) + options &= ~SSL_OP_CIPHER_SERVER_PREFERENCE; + +#ifdef SSL_OP_NO_RENEGOTIATION + options |= SSL_OP_NO_RENEGOTIATION; +#endif + + SSL_CTX_set_options(ctx, options); + +#ifdef SSL_MODE_ASYNC + if (global_ssl.async) + mode |= SSL_MODE_ASYNC; +#endif + SSL_CTX_set_mode(ctx, mode); + if (global_ssl.life_time) + SSL_CTX_set_timeout(ctx, global_ssl.life_time); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME +# ifdef OPENSSL_IS_BORINGSSL + SSL_CTX_set_select_certificate_cb(ctx, ssl_sock_switchctx_cbk); + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk); +# elif defined(HAVE_SSL_CLIENT_HELLO_CB) +# if defined(SSL_OP_NO_ANTI_REPLAY) + if (bind_conf->ssl_conf.early_data) + SSL_CTX_set_options(ctx, SSL_OP_NO_ANTI_REPLAY); +# endif /* ! SSL_OP_NO_ANTI_REPLAY */ + SSL_CTX_set_client_hello_cb(ctx, ssl_sock_switchctx_cbk, NULL); + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_err_cbk); +# elif 0 && defined(USE_OPENSSL_WOLFSSL) + SSL_CTX_set_cert_cb(ctx, ssl_sock_switchctx_wolfSSL_cbk, bind_conf); +# else + /* ! OPENSSL_IS_BORINGSSL && ! HAVE_SSL_CLIENT_HELLO_CB */ + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_sock_switchctx_cbk); +# endif + SSL_CTX_set_tlsext_servername_arg(ctx, bind_conf); +#endif /* ! SSL_CTRL_SET_TLSEXT_HOSTNAME */ + return cfgerr; +} + + +static inline void sh_ssl_sess_free_blocks(struct shared_block *first, void *data) +{ + struct sh_ssl_sess_hdr *sh_ssl_sess = (struct sh_ssl_sess_hdr *)first->data; + if (first->len > 0) + sh_ssl_sess_tree_delete(sh_ssl_sess); +} + +/* return first block from sh_ssl_sess */ +static inline struct shared_block *sh_ssl_sess_first_block(struct sh_ssl_sess_hdr *sh_ssl_sess) +{ + return (struct shared_block *)((unsigned char *)sh_ssl_sess - offsetof(struct shared_block, data)); + +} + +/* store a session into the cache + * s_id : session id padded with zero to SSL_MAX_SSL_SESSION_ID_LENGTH + * data: asn1 encoded session + * data_len: asn1 encoded session length + * Returns 1 id session was stored (else 0) + */ +static int sh_ssl_sess_store(unsigned char *s_id, unsigned char *data, int data_len) +{ + struct shared_block *first; + struct sh_ssl_sess_hdr *sh_ssl_sess, *oldsh_ssl_sess; + + first = shctx_row_reserve_hot(ssl_shctx, NULL, data_len + sizeof(struct sh_ssl_sess_hdr)); + if (!first) { + /* Could not retrieve enough free blocks to store that session */ + return 0; + } + + shctx_wrlock(ssl_shctx); + + /* STORE the key in the first elem */ + sh_ssl_sess = (struct sh_ssl_sess_hdr *)first->data; + memcpy(sh_ssl_sess->key_data, s_id, SSL_MAX_SSL_SESSION_ID_LENGTH); + first->len = sizeof(struct sh_ssl_sess_hdr); + + /* it returns the already existing node + or current node if none, never returns null */ + oldsh_ssl_sess = sh_ssl_sess_tree_insert(sh_ssl_sess); + if (oldsh_ssl_sess != sh_ssl_sess) { + /* NOTE: Row couldn't be in use because we lock read & write function */ + /* release the reserved row */ + first->len = 0; /* the len must be liberated in order not to call the release callback on it */ + shctx_row_reattach(ssl_shctx, first); + /* replace the previous session already in the tree */ + sh_ssl_sess = oldsh_ssl_sess; + /* ignore the previous session data, only use the header */ + first = sh_ssl_sess_first_block(sh_ssl_sess); + shctx_row_detach(ssl_shctx, first); + first->len = sizeof(struct sh_ssl_sess_hdr); + } + + if (shctx_row_data_append(ssl_shctx, first, data, data_len) < 0) { + shctx_row_reattach(ssl_shctx, first); + return 0; + } + + shctx_row_reattach(ssl_shctx, first); + + shctx_wrunlock(ssl_shctx); + + return 1; +} + +/* SSL callback used when a new session is created while connecting to a server */ +static int ssl_sess_new_srv_cb(SSL *ssl, SSL_SESSION *sess) +{ + struct connection *conn = SSL_get_ex_data(ssl, ssl_app_data_index); + struct server *s; + uint old_tid; + + s = __objt_server(conn->target); + + /* RWLOCK: only read lock the SSL cache even when writing in it because there is + * one cache per thread, it only prevents to flush it from the CLI in + * another thread. However, we also write-lock our session element while + * updating it to make sure no other thread is reading it while we're copying + * or releasing it. + */ + + if (!(s->ssl_ctx.options & SRV_SSL_O_NO_REUSE)) { + int len; + unsigned char *ptr; + const char *sni; + + /* determine the required len to store this new session */ + len = i2d_SSL_SESSION(sess, NULL); + sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + HA_RWLOCK_RDLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + + ptr = s->ssl_ctx.reused_sess[tid].ptr; + + /* we're updating the possibly shared session right now */ + HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + + if (!ptr || s->ssl_ctx.reused_sess[tid].allocated_size < len) { + /* insufficient storage, reallocate */ + len = (len + 7) & -8; /* round to the nearest 8 bytes */ + ptr = realloc(ptr, len); + if (!ptr) + free(s->ssl_ctx.reused_sess[tid].ptr); + s->ssl_ctx.reused_sess[tid].ptr = ptr; + s->ssl_ctx.reused_sess[tid].allocated_size = len; + } + + if (ptr) { + /* store the new session into ptr and advance it; save the + * resulting size. It's guaranteed to be equal to the returned + * len above, and the pointer to be advanced by as much. + */ + s->ssl_ctx.reused_sess[tid].size = i2d_SSL_SESSION(sess, &ptr); + } + + /* done updating the session */ + + /* Now we'll try to add or remove this entry as a valid one: + * - if no entry is set and we have one, let's share it + * - if our entry was set and we have no more, let's clear it + */ + old_tid = HA_ATOMIC_LOAD(&s->ssl_ctx.last_ssl_sess_tid); // 0=none, >0 = tid + 1 + if (!s->ssl_ctx.reused_sess[tid].ptr && old_tid == tid + 1) + HA_ATOMIC_CAS(&s->ssl_ctx.last_ssl_sess_tid, &old_tid, 0); // no more valid + else if (s->ssl_ctx.reused_sess[tid].ptr && !old_tid) + HA_ATOMIC_CAS(&s->ssl_ctx.last_ssl_sess_tid, &old_tid, tid + 1); + + if (s->ssl_ctx.reused_sess[tid].sni) { + /* if the new sni is empty or isn' t the same as the old one */ + if ((!sni) || strcmp(s->ssl_ctx.reused_sess[tid].sni, sni) != 0) { + ha_free(&s->ssl_ctx.reused_sess[tid].sni); + if (sni) + s->ssl_ctx.reused_sess[tid].sni = strdup(sni); + } + } else if (sni) { + /* if there wasn't an old sni but there is a new one */ + s->ssl_ctx.reused_sess[tid].sni = strdup(sni); + } + HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + HA_RWLOCK_RDUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + } else { + HA_RWLOCK_RDLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + + if (s->ssl_ctx.reused_sess[tid].ptr) { + HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + ha_free(&s->ssl_ctx.reused_sess[tid].ptr); + HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + } + + old_tid = HA_ATOMIC_LOAD(&s->ssl_ctx.last_ssl_sess_tid); // 0=none, >0 = tid + 1 + if (old_tid == tid + 1) + HA_ATOMIC_CAS(&s->ssl_ctx.last_ssl_sess_tid, &old_tid, 0); // no more valid + + HA_RWLOCK_RDUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + } + + return 0; +} + + +/* SSL callback used on new session creation */ +int sh_ssl_sess_new_cb(SSL *ssl, SSL_SESSION *sess) +{ + unsigned char encsess[SHSESS_MAX_DATA_LEN]; /* encoded session */ + unsigned char encid[SSL_MAX_SSL_SESSION_ID_LENGTH]; /* encoded id */ + unsigned char *p; + int data_len; + unsigned int sid_length; + const unsigned char *sid_data; + + /* Session id is already stored in to key and session id is known + * so we don't store it to keep size. + * note: SSL_SESSION_set1_id is using + * a memcpy so we need to use a different pointer + * than sid_data or sid_ctx_data to avoid valgrind + * complaining. + */ + + sid_data = SSL_SESSION_get_id(sess, &sid_length); + + /* copy value in an other buffer */ + memcpy(encid, sid_data, sid_length); + + /* pad with 0 */ + if (sid_length < SSL_MAX_SSL_SESSION_ID_LENGTH) + memset(encid + sid_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sid_length); + + /* force length to zero to avoid ASN1 encoding */ + SSL_SESSION_set1_id(sess, encid, 0); + + /* force length to zero to avoid ASN1 encoding */ + SSL_SESSION_set1_id_context(sess, (const unsigned char *)SHCTX_APPNAME, 0); + + /* check if buffer is large enough for the ASN1 encoded session */ + data_len = i2d_SSL_SESSION(sess, NULL); + if (data_len > SHSESS_MAX_DATA_LEN) + goto err; + + p = encsess; + + /* process ASN1 session encoding before the lock */ + i2d_SSL_SESSION(sess, &p); + + + /* store to cache */ + sh_ssl_sess_store(encid, encsess, data_len); +err: + /* reset original length values */ + SSL_SESSION_set1_id(sess, encid, sid_length); + SSL_SESSION_set1_id_context(sess, (const unsigned char *)SHCTX_APPNAME, strlen(SHCTX_APPNAME)); + + return 0; /* do not increment session reference count */ +} + +/* SSL callback used on lookup an existing session cause none found in internal cache */ +SSL_SESSION *sh_ssl_sess_get_cb(SSL *ssl, __OPENSSL_110_CONST__ unsigned char *key, int key_len, int *do_copy) +{ + struct sh_ssl_sess_hdr *sh_ssl_sess; + unsigned char data[SHSESS_MAX_DATA_LEN], *p; + unsigned char tmpkey[SSL_MAX_SSL_SESSION_ID_LENGTH]; + SSL_SESSION *sess; + struct shared_block *first; + + _HA_ATOMIC_INC(&global.shctx_lookups); + + /* allow the session to be freed automatically by openssl */ + *do_copy = 0; + + /* tree key is zeros padded sessionid */ + if (key_len < SSL_MAX_SSL_SESSION_ID_LENGTH) { + memcpy(tmpkey, key, key_len); + memset(tmpkey + key_len, 0, SSL_MAX_SSL_SESSION_ID_LENGTH - key_len); + key = tmpkey; + } + + /* lock cache */ + shctx_wrlock(ssl_shctx); + + /* lookup for session */ + sh_ssl_sess = sh_ssl_sess_tree_lookup(key); + if (!sh_ssl_sess) { + /* no session found: unlock cache and exit */ + shctx_wrunlock(ssl_shctx); + _HA_ATOMIC_INC(&global.shctx_misses); + return NULL; + } + + /* sh_ssl_sess (shared_block->data) is at the end of shared_block */ + first = sh_ssl_sess_first_block(sh_ssl_sess); + + shctx_row_data_get(ssl_shctx, first, data, sizeof(struct sh_ssl_sess_hdr), first->len-sizeof(struct sh_ssl_sess_hdr)); + + shctx_wrunlock(ssl_shctx); + + /* decode ASN1 session */ + p = data; + sess = d2i_SSL_SESSION(NULL, (const unsigned char **)&p, first->len-sizeof(struct sh_ssl_sess_hdr)); + /* Reset session id and session id contenxt */ + if (sess) { + SSL_SESSION_set1_id(sess, key, key_len); + SSL_SESSION_set1_id_context(sess, (const unsigned char *)SHCTX_APPNAME, strlen(SHCTX_APPNAME)); + } + + return sess; +} + + +/* SSL callback used to signal session is no more used in internal cache */ +void sh_ssl_sess_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) +{ + struct sh_ssl_sess_hdr *sh_ssl_sess; + unsigned char tmpkey[SSL_MAX_SSL_SESSION_ID_LENGTH]; + unsigned int sid_length; + const unsigned char *sid_data; + (void)ctx; + + sid_data = SSL_SESSION_get_id(sess, &sid_length); + /* tree key is zeros padded sessionid */ + if (sid_length < SSL_MAX_SSL_SESSION_ID_LENGTH) { + memcpy(tmpkey, sid_data, sid_length); + memset(tmpkey+sid_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH - sid_length); + sid_data = tmpkey; + } + + shctx_wrlock(ssl_shctx); + + /* lookup for session */ + sh_ssl_sess = sh_ssl_sess_tree_lookup(sid_data); + if (sh_ssl_sess) { + /* free session */ + sh_ssl_sess_tree_delete(sh_ssl_sess); + } + + /* unlock cache */ + shctx_wrunlock(ssl_shctx); +} + +/* Set session cache mode to server and disable openssl internal cache. + * Set shared cache callbacks on an ssl context. + * Shared context MUST be firstly initialized */ +void ssl_set_shctx(SSL_CTX *ctx) +{ + SSL_CTX_set_session_id_context(ctx, (const unsigned char *)SHCTX_APPNAME, strlen(SHCTX_APPNAME)); + + if (!ssl_shctx) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + return; + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | + SSL_SESS_CACHE_NO_INTERNAL | + SSL_SESS_CACHE_NO_AUTO_CLEAR); + + /* Set callbacks */ + SSL_CTX_sess_set_new_cb(ctx, sh_ssl_sess_new_cb); + SSL_CTX_sess_set_get_cb(ctx, sh_ssl_sess_get_cb); + SSL_CTX_sess_set_remove_cb(ctx, sh_ssl_sess_remove_cb); +} + +/* + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format + * + * The format is: + * *