src/tls/xqc_tls_ctx.c (365 lines of code) (raw):

/** * @copyright Copyright (c) 2022, Alibaba Group Holding Limited */ #include "xqc_tls_ctx.h" #include "xqc_tls_defs.h" #include "xqc_ssl_cbs.h" #include "xqc_ssl_if.h" #include "src/common/xqc_malloc.h" typedef struct xqc_tls_ctx_s { xqc_tls_type_t type; /* ssl context */ SSL_CTX *ssl_ctx; /* general config for ssl */ xqc_engine_ssl_config_t cfg; /* callback functions for tls connection */ xqc_tls_callbacks_t tls_cbs; /* session ticket key */ xqc_ssl_session_ticket_key_t session_ticket_key; /* log handler */ xqc_log_t *log; /* the buffer of alpn, for server alpn selection */ unsigned char *alpn_list; size_t alpn_list_sz; size_t alpn_list_len; } xqc_tls_ctx_t; xqc_int_t xqc_create_client_ssl_ctx(xqc_tls_ctx_t *ctx) { SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); if (NULL == ssl_ctx) { xqc_log(ctx->log, XQC_LOG_ERROR, "|create client SSL_CTX error|%s", ERR_error_string(ERR_get_error(), NULL)); return -XQC_TLS_INTERNAL; } /* set tls version */ SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); if (SSL_CTX_set1_curves_list(ssl_ctx, ctx->cfg.groups) != XQC_SSL_SUCCESS) { xqc_log(ctx->log, XQC_LOG_ERROR, "|SSL_CTX_set1_groups_list failed| error info:%s|", ERR_error_string(ERR_get_error(), NULL)); goto fail; } /* enable session cache */ SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE); /* set session ticket callback */ SSL_CTX_sess_set_new_cb(ssl_ctx, xqc_ssl_new_session_cb); /* set the lifetime of session */ xqc_ssl_ctx_set_timeout(ssl_ctx, ctx->cfg.session_timeout); ctx->ssl_ctx = ssl_ctx; return XQC_OK; fail: SSL_CTX_free(ssl_ctx); return -XQC_TLS_INTERNAL; } xqc_int_t xqc_create_server_ssl_ctx(xqc_tls_ctx_t *ctx) { SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); if (NULL == ssl_ctx) { xqc_log(ctx->log, XQC_LOG_ERROR, "|create server SSL_CTX error|%s", ERR_error_string(ERR_get_error(), NULL)); return -XQC_TLS_INTERNAL; } /* set tls version */ SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); /* set context options */ long ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | SSL_OP_SINGLE_ECDH_USE #ifdef SSL_OP_NO_ANTI_REPLAY | SSL_OP_NO_ANTI_REPLAY #endif ; SSL_CTX_set_options(ssl_ctx, ssl_opts); /* Save RAM by releasing read and write buffers when they're empty */ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); /* set curves list */ if (SSL_CTX_set1_curves_list(ssl_ctx, ctx->cfg.groups) != XQC_SSL_SUCCESS) { xqc_log(ctx->log, XQC_LOG_ERROR, "|SSL_CTX_set1_groups_list failed| error info:%s|", ERR_error_string(ERR_get_error(), NULL)); goto fail; } /* set private key file */ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, ctx->cfg.private_key_file, SSL_FILETYPE_PEM) != XQC_SSL_SUCCESS) { xqc_log(ctx->log, XQC_LOG_ERROR, "|SSL_CTX_use_PrivateKey_file| error info:%s|", ERR_error_string(ERR_get_error(), NULL)); goto fail; } /* set cert file */ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, ctx->cfg.cert_file) != XQC_SSL_SUCCESS) { xqc_log(ctx->log, XQC_LOG_ERROR, "|SSL_CTX_use_PrivateKey_file| error info:%s|", ERR_error_string(ERR_get_error(), NULL)); goto fail; } /* check private key of certificate */ if (SSL_CTX_check_private_key(ssl_ctx) != XQC_SSL_SUCCESS) { xqc_log(ctx->log, XQC_LOG_ERROR, "|SSL_CTX_check_private_key| error info:%s|", ERR_error_string(ERR_get_error(), NULL)); goto fail; } /* set session ticket key callback */ if (ctx->cfg.session_ticket_key_len == 0 || ctx->cfg.session_ticket_key_data == NULL) { xqc_log(ctx->log, XQC_LOG_WARN, "|read ssl session ticket key error|"); } else { SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, xqc_ssl_session_ticket_key_cb); } SSL_CTX_set_cert_cb(ssl_ctx, xqc_ssl_cert_cb, ctx); SSL_CTX_set_default_verify_paths(ssl_ctx); SSL_CTX_set_alpn_select_cb(ssl_ctx, xqc_ssl_alpn_select_cb, ctx); xqc_ssl_ctx_enable_max_early_data(ssl_ctx); xqc_ssl_ctx_set_timeout(ssl_ctx, ctx->cfg.session_timeout); ctx->ssl_ctx = ssl_ctx; return XQC_OK; fail: SSL_CTX_free(ssl_ctx); return -XQC_TLS_INTERNAL; } xqc_int_t xqc_init_session_ticket_keys(xqc_ssl_session_ticket_key_t *key, char *session_key_data, size_t session_key_len) { if (session_key_len != 48 && session_key_len != 80) { return -XQC_TLS_INVALID_ARGUMENT; } memset(key, 0, sizeof(xqc_ssl_session_ticket_key_t)); if (session_key_len == 48) { key->size = 48; memcpy(key->name, session_key_data, 16); memcpy(key->aes_key, session_key_data + 16, 16); memcpy(key->hmac_key, session_key_data + 32, 16); } else { key->size = 80; memcpy(key->name, session_key_data, 16); memcpy(key->hmac_key, session_key_data + 16, 32); memcpy(key->aes_key, session_key_data + 48, 32); } return XQC_OK; } xqc_int_t xqc_tls_ctx_set_config(xqc_tls_ctx_t *ctx, const xqc_engine_ssl_config_t *src) { xqc_int_t ret = XQC_OK; xqc_engine_ssl_config_t *dst = &ctx->cfg; dst->session_timeout = src->session_timeout; /* copy ciphers */ if (src->ciphers && *src->ciphers) { int len = strlen(src->ciphers) + 1; dst->ciphers = (char *)xqc_malloc(len); if (dst->ciphers == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|ciphers malloc error|"); return -XQC_EMALLOC; } memcpy(dst->ciphers, src->ciphers, len); } else { int len = sizeof(XQC_TLS_CIPHERS); dst->ciphers = (char *)xqc_malloc(len); if (dst->ciphers == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|ciphers malloc error|"); return -XQC_EMALLOC; } memcpy(dst->ciphers, XQC_TLS_CIPHERS, len); } /* copy curves list */ if (src->groups && *src->groups) { int len = strlen(src->groups) + 1; dst->groups = (char *)xqc_malloc(len); if (dst->groups == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|groups malloc error|"); return -XQC_EMALLOC; } memcpy(dst->groups, src->groups, len); } else { int len = sizeof(XQC_TLS_GROUPS); dst->groups = (char *)xqc_malloc(len); if (dst->groups == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|groups malloc error|"); return -XQC_EMALLOC; } memcpy(dst->groups, XQC_TLS_GROUPS, len); } if (ctx->type == XQC_TLS_TYPE_SERVER) { /* copy private key file */ if (src->private_key_file && *src->private_key_file) { int len = strlen(src->private_key_file) + 1; dst->private_key_file = (char *)xqc_malloc(len); if (dst->private_key_file == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|private_key_file malloc error|"); return -XQC_EMALLOC; } memcpy(dst->private_key_file, src->private_key_file, len); } else { xqc_log(ctx->log, XQC_LOG_ERROR, "|no private key file|"); return -XQC_TLS_INVALID_ARGUMENT; } /* copy cert file */ if (src->cert_file && *src->cert_file) { int len = strlen(src->cert_file) + 1; dst->cert_file = (char *)xqc_malloc(len); if (dst->cert_file == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|cert_file malloc error|"); return -XQC_EMALLOC; } memcpy(dst->cert_file, src->cert_file, len); } else { xqc_log(ctx->log, XQC_LOG_ERROR, "|no cert file|"); return -XQC_TLS_INVALID_ARGUMENT; } /* copy and init session ticket key */ if (src->session_ticket_key_len > 0) { dst->session_ticket_key_len = src->session_ticket_key_len; dst->session_ticket_key_data = (char *)xqc_malloc(src->session_ticket_key_len); if (dst->session_ticket_key_data == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|session ticket key data malloc error|"); return -XQC_EMALLOC; } memcpy(dst->session_ticket_key_data, src->session_ticket_key_data, src->session_ticket_key_len); /* init session ticket key */ if (xqc_init_session_ticket_keys(&ctx->session_ticket_key, dst->session_ticket_key_data, dst->session_ticket_key_len) < 0) { xqc_log(ctx->log, XQC_LOG_ERROR, "|read session ticket key error|"); return -XQC_TLS_INVALID_ARGUMENT; } } else { dst->session_ticket_key_len = 0; dst->session_ticket_key_data = NULL; xqc_log(ctx->log, XQC_LOG_WARN, "|no session ticket key data|"); } } return XQC_OK; } xqc_tls_ctx_t * xqc_tls_ctx_create(xqc_tls_type_t type, const xqc_engine_ssl_config_t *cfg, const xqc_tls_callbacks_t *cbs, xqc_log_t *log) { xqc_tls_ctx_t *ctx = xqc_calloc(1, sizeof(xqc_tls_ctx_t)); if (NULL == ctx) { xqc_log(log, XQC_LOG_ERROR, "|calloc memory for tls ctx error|"); return NULL; } ctx->type = type; ctx->tls_cbs = *cbs; ctx->log = log; /* copy config */ xqc_int_t ret = xqc_tls_ctx_set_config(ctx, cfg); if (ret != XQC_OK) { goto fail; } /* init ssl context */ if (type == XQC_TLS_TYPE_SERVER) { ret = xqc_create_server_ssl_ctx(ctx); } else { ret = xqc_create_client_ssl_ctx(ctx); } if (ret != XQC_OK) { goto fail; } /* set cipher suites */ if (cfg->ciphers) { ret = xqc_ssl_ctx_set_cipher_suites(ctx->ssl_ctx, cfg->ciphers); if (ret != XQC_OK) { xqc_log(ctx->log, XQC_LOG_INFO, "|set cipher suites fail|"); goto fail; } xqc_log(ctx->log, XQC_LOG_INFO, "|set cipher suites suc|ciphers:%s", cfg->ciphers); } /* set keylog callback, this will be callback to xqc_tls_t */ if (cbs->keylog_cb) { SSL_CTX_set_keylog_callback(ctx->ssl_ctx, xqc_ssl_keylog_cb); } return ctx; fail: xqc_tls_ctx_destroy(ctx); return NULL; } void xqc_tls_ctx_free_cfg(xqc_tls_ctx_t *ctx) { xqc_engine_ssl_config_t *cfg = &ctx->cfg; if (cfg->ciphers) { xqc_free(ctx->cfg.ciphers); } if (cfg->groups) { xqc_free(cfg->groups); } if (cfg->private_key_file) { xqc_free(cfg->private_key_file); } if (cfg->cert_file) { xqc_free(cfg->cert_file); } if (cfg->session_ticket_key_data) { xqc_free(cfg->session_ticket_key_data); } } void xqc_tls_ctx_destroy(xqc_tls_ctx_t *ctx) { if (ctx != NULL) { SSL_CTX_free(ctx->ssl_ctx); /* free config memory */ xqc_tls_ctx_free_cfg(ctx); /* free alpn selection buffer */ if (ctx->alpn_list) { xqc_free(ctx->alpn_list); ctx->alpn_list = NULL; } xqc_free(ctx); ctx = NULL; } } SSL_CTX * xqc_tls_ctx_get_ssl_ctx(xqc_tls_ctx_t *ctx) { return ctx->ssl_ctx; } xqc_tls_type_t xqc_tls_ctx_get_type(xqc_tls_ctx_t *ctx) { return ctx->type; } void xqc_tls_ctx_get_tls_callbacks(xqc_tls_ctx_t *ctx, xqc_tls_callbacks_t **tls_cbs) { *tls_cbs = &ctx->tls_cbs; } void xqc_tls_ctx_get_session_ticket_key(xqc_tls_ctx_t *ctx, xqc_ssl_session_ticket_key_t **stk) { *stk = &ctx->session_ticket_key; } void xqc_tls_ctx_get_cfg(xqc_tls_ctx_t *ctx, xqc_engine_ssl_config_t **cfg) { *cfg = &ctx->cfg; } xqc_int_t xqc_tls_ctx_register_alpn(xqc_tls_ctx_t *ctx, const char *alpn, size_t alpn_len) { xqc_list_head_t *pos, *next; if (NULL == alpn || 0 == alpn_len) { return -XQC_EPARAM; } if (alpn_len + 1 > ctx->alpn_list_sz - ctx->alpn_list_len) { /* realloc buffer */ size_t new_alpn_list_sz = 2 * (ctx->alpn_list_sz + alpn_len) + 1; char *alpn_list_new = xqc_malloc(new_alpn_list_sz); if (alpn_list_new == NULL) { xqc_log(ctx->log, XQC_LOG_ERROR, "|alpn list malloc error|"); return -XQC_EMALLOC; } ctx->alpn_list_sz = new_alpn_list_sz; /* copy alpn_list */ xqc_memcpy(alpn_list_new, ctx->alpn_list, ctx->alpn_list_len); alpn_list_new[ctx->alpn_list_len] = '\0'; /* replace */ xqc_free(ctx->alpn_list); ctx->alpn_list = alpn_list_new; } /* sprintf new alpn to the end of alpn_list buffer */ snprintf(ctx->alpn_list + ctx->alpn_list_len, ctx->alpn_list_sz - ctx->alpn_list_len, "%c%s", (uint8_t)alpn_len, alpn); ctx->alpn_list_len = strlen(ctx->alpn_list); xqc_log(ctx->log, XQC_LOG_INFO, "|alpn registered|alpn:%s|alpn_list:%s", alpn, ctx->alpn_list); return XQC_OK; } xqc_int_t xqc_tls_ctx_unregister_alpn(xqc_tls_ctx_t *ctx, const char *alpn, size_t alpn_len) { if (NULL == alpn || 0 == alpn_len) { return -XQC_EPARAM; } unsigned char *pos = ctx->alpn_list; unsigned char *end = ctx->alpn_list + ctx->alpn_list_len; while (pos < end) { size_t node_len = *pos; /* length for current alpn node */ unsigned char *next_node = pos + node_len + 1; if (node_len == alpn_len) { int cmp_res = memcmp(pos + 1, alpn, alpn_len); if (cmp_res == 0) { /* found alpn, delete it */ size_t remain_len = end - next_node; memmove(pos, next_node, remain_len); ctx->alpn_list_len -= alpn_len + 1; return XQC_OK; } } /* move to next node */ pos = next_node; } return -XQC_EALPN_NOT_REGISTERED; } void xqc_tls_ctx_get_alpn_list(xqc_tls_ctx_t *ctx, unsigned char **alpn_list, size_t *alpn_list_len) { *alpn_list = ctx->alpn_list; *alpn_list_len = ctx->alpn_list_len; }