From: Florin Coras Date: Mon, 21 Jul 2025 00:57:29 +0000 (-0400) Subject: session tls: scaffolding for async cert retrieval X-Git-Tag: v26.02-rc0~131 X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F60%2F43460%2F8;p=vpp.git session tls: scaffolding for async cert retrieval Basic experimental infrastructure for server async retrieval. Type: improvement Change-Id: Iec48a0a30e5968a42237b810ec5e6c4e9d633728 Signed-off-by: Florin Coras --- diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 7305fe6afda..08ae12a1f18 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -953,29 +953,42 @@ openssl_alpn_select_cb (SSL *ssl, const unsigned char **out, static int openssl_start_listen (tls_ctx_t * lctx) { + u64 flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; + openssl_main_t *om = &openssl_main; const SSL_METHOD *method; SSL_CTX *ssl_ctx; int rv; u32 olc_index; openssl_listen_ctx_t *olc; app_cert_key_pair_t *ckpair; - app_certkey_int_ctx_t *cki; + app_certkey_int_ctx_t *cki = 0; - long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; - openssl_main_t *om = &openssl_main; + if (lctx->ckpair_index) + { + ckpair = app_cert_key_pair_get_if_valid (lctx->ckpair_index); + if (!ckpair) + return -1; - ckpair = app_cert_key_pair_get_if_valid (lctx->ckpair_index); - if (!ckpair) - return -1; + if (!ckpair->cert || !ckpair->key) + { + TLS_DBG (1, "tls cert and/or key not configured %d", + lctx->parent_app_wrk_index); + return -1; + } - if (!ckpair->cert || !ckpair->key) - { - TLS_DBG (1, "tls cert and/or key not configured %d", - lctx->parent_app_wrk_index); - return -1; + cki = app_certkey_get_int_ctx (ckpair, lctx->c_thread_index); + if (!cki || !cki->cert) + { + cki = openssl_init_certkey_init_ctx (ckpair, lctx->c_thread_index); + if (!cki) + { + clib_warning ("unable to initialize certificate/key pair"); + return -1; + } + } } - method = lctx->tls_type == TRANSPORT_PROTO_TLS ? SSLv23_server_method () : + method = lctx->tls_type == TRANSPORT_PROTO_TLS ? TLS_server_method () : DTLS_server_method (); ssl_ctx = SSL_CTX_new (method); if (!ssl_ctx) @@ -1046,22 +1059,18 @@ openssl_start_listen (tls_ctx_t * lctx) /* * Set the key and cert */ - cki = app_certkey_get_int_ctx (ckpair, lctx->c_thread_index); - if (!cki || !cki->cert) + if (cki) { - cki = openssl_init_certkey_init_ctx (ckpair, lctx->c_thread_index); - if (!cki) + rv = SSL_CTX_use_certificate (ssl_ctx, cki->cert); + if (rv != 1) { - clib_warning ("unable to initialize certificate/key pair"); - return -1; + clib_warning ("unable to use SSL certificate"); + goto err; } } - - rv = SSL_CTX_use_certificate (ssl_ctx, cki->cert); - if (rv != 1) + else { - clib_warning ("unable to use SSL certificate"); - goto err; + lctx->flags |= TLS_CONN_F_ASYNC_CERT; } rv = SSL_CTX_use_PrivateKey (ssl_ctx, cki->key); @@ -1103,6 +1112,126 @@ openssl_stop_listen (tls_ctx_t * lctx) return 0; } +static void +openssl_server_async_cert_cb (app_crypto_async_reply_t *reply) +{ + app_crypto_async_req_handle_t handle = reply->handle; + clib_thread_index_t thread_index; + app_cert_key_pair_t *ckpair; + app_certkey_int_ctx_t *cki; + openssl_ctx_t *oc; + tls_ctx_t *ctx; + + thread_index = handle.thread_index; + ASSERT (thread_index == vlib_get_thread_index ()); + ctx = openssl_ctx_get_w_thread (handle.opaque & TLS_IDX_MASK, + handle.thread_index); + oc = (openssl_ctx_t *) ctx; + + ckpair = app_cert_key_pair_get_if_valid (reply->async_cert.ckpair_index); + if (!ckpair || !ckpair->cert || !ckpair->key) + { + TLS_DBG (1, "Invalid certificate/key pair %u", ckpair_index); + goto error; + } + + /* Get or initialize certificate context */ + cki = app_certkey_get_int_ctx (ckpair, thread_index); + if (!cki || !cki->cert) + { + cki = openssl_init_certkey_init_ctx (ckpair, thread_index); + if (!cki) + { + TLS_DBG (1, "Failed to initialize certificate context"); + goto error; + } + } + + /* Set the certificate and private key */ + if (SSL_use_certificate (oc->ssl, cki->cert) != 1) + { + TLS_DBG (1, "Failed to set certificate"); + goto error; + } + + if (SSL_use_PrivateKey (oc->ssl, cki->key) != 1) + { + TLS_DBG (1, "Failed to set private key"); + goto error; + } + + /* Verify that the private key matches the certificate */ + if (SSL_check_private_key (oc->ssl) != 1) + { + TLS_DBG (1, "Private key does not match certificate"); + goto error; + } + + TLS_DBG (2, "Successfully set certificate for server %s", + SSL_get_servername (oc->ssl, TLSEXT_NAMETYPE_host_name)); + + /* Continue handshake */ + while (1) + { + int rv, err; + rv = SSL_do_handshake (oc->ssl); + err = SSL_get_error (oc->ssl, rv); + if (openssl_main.async && err == SSL_ERROR_WANT_ASYNC) + break; + + if (err != SSL_ERROR_WANT_WRITE) + break; + } + + return; + +error: + /* Do not free ctx yet, in case we have pending rx events */ + ctx->flags |= TLS_CONN_F_NO_APP_SESSION; + tls_disconnect_transport (ctx); +} + +static int +openssl_server_cert_callback (SSL *ssl, void *arg) +{ + u32 ctx_index = (u32) pointer_to_uword (arg); + tls_ctx_t *ctx = openssl_ctx_get (ctx_index); + openssl_ctx_t *oc = (openssl_ctx_t *) ctx; + const char *servername; + + TLS_DBG (2, "Server certificate callback invoked for ctx %u", + oc->openssl_ctx_index); + + /* Get the requested server name from SNI extension */ + servername = SSL_get_servername (ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) + { + /* TODO maybe handle using default cert, if provided */ + TLS_DBG (1, "No SNI provided"); + return 0; + } + + TLS_DBG (2, "SNI requested server name: %s", servername); + + app_crypto_async_req_t req = { .req_type = APP_CRYPTO_ASYNC_REQ_TYPE_CERT, + .handle = { + .thread_index = ctx->c_thread_index, + .opaque = ctx->tls_ctx_handle, + }, + .cb = openssl_server_async_cert_cb, + .app_wrk_index = ctx->parent_app_wrk_index, + .async_cert = { + .servername = (const u8 *) servername, + } }; + + oc->req_ticket = app_crypto_async_req (&req); + if (oc->req_ticket.as_u64 == APP_CRYPTO_ASYNC_INVALID_TICKET.as_u64) + return 0; + + /* Certificate selection in progress */ + return -1; +} + static int openssl_ctx_init_server (tls_ctx_t * ctx) { @@ -1145,6 +1274,16 @@ openssl_ctx_init_server (tls_ctx_t * ctx) openssl_ctx_handshake_rx (ctx, tls_session); } + /* Set up certificate callback for async certificate retrieval */ + if (ctx->flags & TLS_CONN_F_ASYNC_CERT) + { + uword oc_index = oc->openssl_ctx_index; + TLS_DBG (2, "Set async cert callback for ctx %u", oc_index); + SSL_set_cert_cb (oc->ssl, openssl_server_cert_callback, + uword_to_pointer (oc_index, void *)); + return 0; + } + while (1) { rv = SSL_do_handshake (oc->ssl); diff --git a/src/plugins/tlsopenssl/tls_openssl.h b/src/plugins/tlsopenssl/tls_openssl.h index f18b579bdb1..bf3440120d6 100644 --- a/src/plugins/tlsopenssl/tls_openssl.h +++ b/src/plugins/tlsopenssl/tls_openssl.h @@ -59,6 +59,7 @@ typedef struct tls_ctx_openssl_ tls_async_ctx_t async_ctx; BIO *rbio; BIO *wbio; + app_crypto_async_req_ticket_t req_ticket; } openssl_ctx_t; typedef struct tls_listen_ctx_opensl_ diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index 2d1b58dbe63..c27e58efcba 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -873,6 +873,8 @@ application_alloc_and_init (app_init_args_t *a) else application_name_table_add (app); + app_crypto_ctx_init (&app->crypto_ctx); + a->app_index = app->app_index; APP_DBG ("New app name: %v api index: %u index %u", app->name, @@ -925,6 +927,9 @@ application_free (application_t * app) */ if (application_is_builtin (app)) application_name_table_del (app); + + app_crypto_ctx_free (&app->crypto_ctx); + vec_free (app->name); pool_put (app_main.app_pool, app); } diff --git a/src/vnet/session/application.h b/src/vnet/session/application.h index 92c182f63ba..b16e42dcadf 100644 --- a/src/vnet/session/application.h +++ b/src/vnet/session/application.h @@ -169,8 +169,17 @@ typedef struct application_ /** collector index, if any */ u32 evt_collector_index; + + /** app crypto state */ + app_crypto_ctx_t crypto_ctx; } application_t; +static inline app_crypto_wrk_t * +app_crypto_wrk_get (application_t *app, clib_thread_index_t thread_index) +{ + return vec_elt_at_index (app->crypto_ctx.wrk, thread_index); +} + typedef struct app_rx_mq_handle_ { union diff --git a/src/vnet/session/application_crypto.c b/src/vnet/session/application_crypto.c index b15799c89ec..c8d26670f62 100644 --- a/src/vnet/session/application_crypto.c +++ b/src/vnet/session/application_crypto.c @@ -108,6 +108,93 @@ vnet_app_del_cert_key_pair (u32 index) return 0; } +app_crypto_async_req_ticket_t +app_crypto_async_req (app_crypto_async_req_t *areq) +{ + app_crypto_async_req_t *req; + app_crypto_wrk_t *crypto_wrk; + app_worker_t *app_wrk; + application_t *app; + app_crypto_async_req_ticket_t ticket; + + app_wrk = app_worker_get (areq->app_wrk_index); + app = application_get (app_wrk->app_index); + if (!app->cb_fns.app_crypto_async) + return APP_CRYPTO_ASYNC_INVALID_TICKET; + + crypto_wrk = app_crypto_wrk_get (app, areq->handle.thread_index); + + /* TODO(fcoras) caching layer */ + + pool_get (crypto_wrk->reqs, req); + *req = *areq; + req->req_index = req - crypto_wrk->reqs; + req->cancelled = 0; + ticket.app_index = app->app_index; + ticket.req_index = req->req_index; + + /* Hand over request to app */ + if (app->cb_fns.app_crypto_async (req)) + return APP_CRYPTO_ASYNC_INVALID_TICKET; + + return ticket; +} + +void +app_crypto_async_cancel_req (app_crypto_async_req_ticket_t ticket) +{ + application_t *app = application_get (ticket.app_index); + clib_thread_index_t thread_index = vlib_get_thread_index (); + app_crypto_async_req_t *req; + app_crypto_wrk_t *crypto_wrk; + + crypto_wrk = app_crypto_wrk_get (app, thread_index); + + if (pool_is_free_index (crypto_wrk->reqs, ticket.req_index)) + return; + req = pool_elt_at_index (crypto_wrk->reqs, ticket.req_index); + req->cancelled = 1; +} + +void +app_crypto_async_reply (app_crypto_async_reply_t *reply) +{ + application_t *app = application_get (reply->app_index); + clib_thread_index_t thread_index = reply->handle.thread_index; + app_crypto_wrk_t *crypto_wrk; + app_crypto_async_req_t *req; + + ASSERT (thread_index == vlib_get_thread_index ()); + + crypto_wrk = app_crypto_wrk_get (app, thread_index); + req = pool_elt_at_index (crypto_wrk->reqs, reply->req_index); + + if (req->cancelled) + goto done; + + reply->handle = req->handle; + req->cb (reply); + +done: + pool_put (crypto_wrk->reqs, req); +} + +void +app_crypto_ctx_init (app_crypto_ctx_t *crypto_ctx) +{ + vec_validate (crypto_ctx->wrk, vlib_num_workers ()); +} + +void +app_crypto_ctx_free (app_crypto_ctx_t *crypto_ctx) +{ + app_crypto_wrk_t *crypto_wrk; + + vec_foreach (crypto_wrk, crypto_ctx->wrk) + pool_free (crypto_wrk->reqs); + vec_free (crypto_ctx->wrk); +} + u8 * format_cert_key_pair (u8 *s, va_list *args) { @@ -199,7 +286,7 @@ application_crypto_init () { app_crypto_main_t *acm = &app_crypto_main; - /* Index 0 was originally used by legacy apis, maintain as invalid */ + /* Index 0 is invalid, used to indicate that no cert was provided */ app_cert_key_pair_alloc (); acm->last_crypto_engine = CRYPTO_ENGINE_LAST; diff --git a/src/vnet/session/application_crypto.h b/src/vnet/session/application_crypto.h index f0587eaf38a..743c3592fe7 100644 --- a/src/vnet/session/application_crypto.h +++ b/src/vnet/session/application_crypto.h @@ -56,6 +56,88 @@ typedef struct crypto_ctx_ void *data; /**< protocol specific data */ } crypto_context_t; +typedef union +{ + struct + { + u32 app_index; + u32 req_index; + }; + u64 as_u64; +} app_crypto_async_req_ticket_t; + +#define APP_CRYPTO_ASYNC_INVALID_TICKET \ + ((app_crypto_async_req_ticket_t){ { .app_index = ~0, .req_index = ~0 } }) + +typedef union +{ + struct + { + u32 opaque; /**< opaque metadata */ + clib_thread_index_t thread_index; /**< thread on which req was made */ + }; + u64 handle; +} app_crypto_async_req_handle_t; + +struct app_crypto_async_reply_; + +typedef void (*app_crypto_async_req_cb) ( + struct app_crypto_async_reply_ *reply); + +#define foreach_app_crypto_async_req_type _ (CERT, "async-cert") + +typedef enum app_crypto_req_type_ +{ +#define _(a, b) APP_CRYPTO_ASYNC_REQ_TYPE_##a, + foreach_app_crypto_async_req_type +#undef _ +} app_crypto_async_req_type_t; + +typedef struct app_crypto_async_req_ +{ + app_crypto_async_req_type_t req_type; /**< request type */ + app_crypto_async_req_handle_t handle; /**< async request handle */ + app_crypto_async_req_cb cb; /**< callback to invoke on completion */ + u32 app_wrk_index; /**< application worker index */ + u32 req_index; /**< index in crypto worker's request pool */ + u8 cancelled; /**< flag to indicate if cancelled */ + union + { + struct + { + const u8 *servername; /**< server name for SNI */ + } async_cert; /**< async cert request data */ + }; +} app_crypto_async_req_t; + +typedef struct app_crypto_async_reply_ +{ + u32 app_index; /**< app that resolved the request */ + u32 req_index; /**< request index in app crypto pool */ + app_crypto_async_req_handle_t handle; /**< request handle */ + app_crypto_async_req_type_t req_type; /**< request type */ + union + { + struct + { + u32 ckpair_index; /**< certificate key-pair index */ + } async_cert; /**< async cert reply data */ + }; +} app_crypto_async_reply_t; + +typedef struct app_crypto_wrk_ +{ + app_crypto_async_req_t *reqs; +} app_crypto_wrk_t; + +typedef struct app_crypto_ctx_ +{ + app_crypto_wrk_t *wrk; +} app_crypto_ctx_t; + +void app_crypto_ctx_init (app_crypto_ctx_t *crypto_ctx); +void app_crypto_ctx_free (app_crypto_ctx_t *crypto_ctx); + /* * Certificate key-pair management */ @@ -86,6 +168,10 @@ app_certkey_alloc_int_ctx (app_cert_key_pair_t *ck, return vec_elt_at_index (ck->cki, thread_index); } +app_crypto_async_req_ticket_t +app_crypto_async_req (app_crypto_async_req_t *req); +void app_crypto_async_cancel_req (app_crypto_async_req_ticket_t ticket); + /* * Crypto engine management */ diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index b1569f076f1..28963923764 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -78,6 +78,9 @@ typedef struct session_cb_vft_ /** Collect and export session logs */ int (*app_evt_callback) (session_t *s); + + /** Callback to handle async crypto requests */ + int (*app_crypto_async) (app_crypto_async_req_t *req); } session_cb_vft_t; #define foreach_app_init_args \ diff --git a/src/vnet/session/transport_types.h b/src/vnet/session/transport_types.h index 0e412939438..50883033699 100644 --- a/src/vnet/session/transport_types.h +++ b/src/vnet/session/transport_types.h @@ -290,10 +290,10 @@ typedef enum transport_endpt_ext_cfg_type_ typedef struct transport_endpt_crypto_cfg_ { - u32 ckpair_index; + u32 ckpair_index; /**< index of ck pair in application crypto layer */ u8 alpn_protos[4]; /**< ordered by preference for server */ - u8 crypto_engine; - u8 hostname[256]; /**< full domain len is 255 as per rfc 3986 */ + u8 crypto_engine; /**< crypto engine requested */ + u8 hostname[256]; /**< full domain len is 255 as per rfc 3986 */ } transport_endpt_crypto_cfg_t; typedef struct transport_endpt_ext_cfg_ diff --git a/src/vnet/tls/tls.h b/src/vnet/tls/tls.h index 04b5d759495..3ee00667e2f 100644 --- a/src/vnet/tls/tls.h +++ b/src/vnet/tls/tls.h @@ -82,7 +82,8 @@ STATIC_ASSERT (sizeof (tls_ctx_id_t) <= TRANSPORT_CONN_ID_LEN, _ (RESUME, "resume") \ _ (HS_DONE, "handshake-done") \ _ (ASYNC_RD, "async-read") \ - _ (SHUTDOWN_TRANSPORT, "shutdown-transport") + _ (SHUTDOWN_TRANSPORT, "shutdown-transport") \ + _ (ASYNC_CERT, "async-cert") typedef enum tls_conn_flags_bit_ {