session tls: scaffolding for async cert retrieval 60/43460/8
authorFlorin Coras <[email protected]>
Mon, 21 Jul 2025 00:57:29 +0000 (20:57 -0400)
committerDave Barach <[email protected]>
Fri, 1 Aug 2025 23:32:40 +0000 (23:32 +0000)
Basic experimental infrastructure for server async retrieval.

Type: improvement

Change-Id: Iec48a0a30e5968a42237b810ec5e6c4e9d633728
Signed-off-by: Florin Coras <[email protected]>
src/plugins/tlsopenssl/tls_openssl.c
src/plugins/tlsopenssl/tls_openssl.h
src/vnet/session/application.c
src/vnet/session/application.h
src/vnet/session/application_crypto.c
src/vnet/session/application_crypto.h
src/vnet/session/application_interface.h
src/vnet/session/transport_types.h
src/vnet/tls/tls.h

index 7305fe6..08ae12a 100644 (file)
@@ -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);
index f18b579..bf34401 100644 (file)
@@ -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_
index 2d1b58d..c27e58e 100644 (file)
@@ -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);
 }
index 92c182f..b16e42d 100644 (file)
@@ -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
index b15799c..c8d2667 100644 (file)
@@ -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;
index f0587ea..743c359 100644 (file)
@@ -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
  */
index b1569f0..2896392 100644 (file)
@@ -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                  \
index 0e41293..5088303 100644 (file)
@@ -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_
index 04b5d75..3ee0066 100644 (file)
@@ -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_
 {