Merge pull request #422 from h2o/kazuho/async-sign

async signing
diff --git a/include/picotls.h b/include/picotls.h
index cf81b72..83913f9 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -236,6 +236,7 @@
 #define PTLS_ERROR_ESNI_RETRY (PTLS_ERROR_CLASS_INTERNAL + 8)
 #define PTLS_ERROR_REJECT_EARLY_DATA (PTLS_ERROR_CLASS_INTERNAL + 9)
 #define PTLS_ERROR_DELEGATE (PTLS_ERROR_CLASS_INTERNAL + 10)
+#define PTLS_ERROR_ASYNC_OPERATION (PTLS_ERROR_CLASS_INTERNAL + 11)
 
 #define PTLS_ERROR_INCORRECT_BASE64 (PTLS_ERROR_CLASS_INTERNAL + 50)
 #define PTLS_ERROR_PEM_LABEL_NOT_FOUND (PTLS_ERROR_CLASS_INTERNAL + 51)
@@ -692,10 +693,17 @@
 PTLS_CALLBACK_TYPE(int, emit_certificate, ptls_t *tls, ptls_message_emitter_t *emitter, ptls_key_schedule_t *key_sched,
                    ptls_iovec_t context, int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos);
 /**
- * when gerenating CertificateVerify, the core calls the callback to sign the handshake context using the certificate.
+ * context object of an async operation (e.g., RSA signature generation)
  */
-PTLS_CALLBACK_TYPE(int, sign_certificate, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *output, ptls_iovec_t input,
-                   const uint16_t *algorithms, size_t num_algorithms);
+typedef struct st_ptls_async_job_t {
+    void (*destroy_)(struct st_ptls_async_job_t *self);
+} ptls_async_job_t;
+/**
+ * When gerenating CertificateVerify, the core calls the callback to sign the handshake context using the certificate. This callback
+ * supports asynchronous mode; see `ptls_openssl_sign_certificate_t` for more information.
+ */
+PTLS_CALLBACK_TYPE(int, sign_certificate, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm,
+                   ptls_buffer_t *output, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms);
 /**
  * after receiving Certificate, the core calls the callback to verify the certificate chain and to obtain a pointer to a
  * callback that should be used for verifying CertificateVerify. If an error occurs between a successful return from this
@@ -1419,6 +1427,10 @@
  */
 void ptls_set_context(ptls_t *tls, ptls_context_t *ctx);
 /**
+ * get the signature context
+ */
+ptls_async_job_t *ptls_get_async_job(ptls_t *tls);
+/**
  * returns the client-random
  */
 ptls_iovec_t ptls_get_client_random(ptls_t *tls);
diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
index b063204..3a7c3b5 100644
--- a/include/picotls/openssl.h
+++ b/include/picotls/openssl.h
@@ -39,6 +39,13 @@
 #endif
 #endif
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100010L && !defined(LIBRESSL_VERSION_NUMBER)
+#if !defined(OPENSSL_NO_ASYNC)
+#include <openssl/async.h>
+#define PTLS_OPENSSL_HAVE_ASYNC 1
+#endif
+#endif
+
 extern ptls_key_exchange_algorithm_t ptls_openssl_secp256r1;
 #ifdef NID_secp384r1
 #define PTLS_OPENSSL_HAVE_SECP384R1 1
@@ -118,6 +125,13 @@
  */
 int ptls_openssl_create_key_exchange(ptls_key_exchange_context_t **ctx, EVP_PKEY *pkey);
 
+#if PTLS_OPENSSL_HAVE_ASYNC
+/**
+ * Returns the file descriptor of the asynchronous operation in flight.
+ */
+OSSL_ASYNC_FD ptls_openssl_get_async_fd(ptls_t *ptls);
+#endif
+
 struct st_ptls_openssl_signature_scheme_t {
     uint16_t scheme_id;
     const EVP_MD *(*scheme_md)(void);
@@ -127,6 +141,14 @@
     ptls_sign_certificate_t super;
     EVP_PKEY *key;
     const struct st_ptls_openssl_signature_scheme_t *schemes; /* terminated by .scheme_id == UINT16_MAX */
+    /**
+     * When set to true, indicates to the backend that the signature can be generated asynchronously. When the backend decides to
+     * generate the signature asynchronously, `ptls_handshake` will return PTLS_ERROR_ASYNC_OPERATION. When receiving that error
+     * code, the user should call `ptls_openssl_get_async_fd` to obtain the file descriptor that represents the asynchronous
+     * operation and poll it for read. Once the file descriptor becomes readable, the user calls `ptls_handshake` once again to
+     * obtain the handshake messages being generated, or call `ptls_free` to discard TLS state.
+     */
+    unsigned async : 1;
 } ptls_openssl_sign_certificate_t;
 
 int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key);
diff --git a/lib/openssl.c b/lib/openssl.c
index a153765..7674234 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -694,8 +694,106 @@
     }
 }
 
+#if PTLS_OPENSSL_HAVE_ASYNC
+
+struct async_sign_ctx {
+    ptls_async_job_t super;
+    const struct st_ptls_openssl_signature_scheme_t *scheme;
+    EVP_MD_CTX *ctx;
+    ASYNC_WAIT_CTX *waitctx;
+    ASYNC_JOB *job;
+    size_t siglen;
+    uint8_t sig[0]; // must be last, see `async_sign_ctx_new`
+};
+
+static void async_sign_ctx_free(ptls_async_job_t *_self)
+{
+    struct async_sign_ctx *self = (void *)_self;
+
+    /* Once the async operation is complete, the user might call `ptls_free` instead of `ptls_handshake`. In such case, to avoid
+     * desynchronization, let the backend read the result from the socket. The code below is a loop, but it is not going to block;
+     * it is the responsibility of the user to refrain from calling `ptls_free` until the asynchronous operation is complete. */
+    if (self->job != NULL) {
+        int ret;
+        while (ASYNC_start_job(&self->job, self->waitctx, &ret, NULL, NULL, 0) == ASYNC_PAUSE)
+            ;
+    }
+
+    EVP_MD_CTX_destroy(self->ctx);
+    ASYNC_WAIT_CTX_free(self->waitctx);
+    free(self);
+}
+
+static ptls_async_job_t *async_sign_ctx_new(const struct st_ptls_openssl_signature_scheme_t *scheme, EVP_MD_CTX *ctx, size_t siglen)
+{
+    struct async_sign_ctx *self;
+
+    if ((self = malloc(offsetof(struct async_sign_ctx, sig) + siglen)) == NULL)
+        return NULL;
+
+    self->super = (ptls_async_job_t){async_sign_ctx_free};
+    self->scheme = scheme;
+    self->ctx = ctx;
+    self->waitctx = ASYNC_WAIT_CTX_new();
+    self->job = NULL;
+    self->siglen = siglen;
+    memset(self->sig, 0, siglen);
+
+    return &self->super;
+}
+
+OSSL_ASYNC_FD ptls_openssl_get_async_fd(ptls_t *ptls)
+{
+    OSSL_ASYNC_FD fds[1];
+    size_t numfds;
+    struct async_sign_ctx *async = (void *)ptls_get_async_job(ptls);
+    assert(async != NULL);
+    ASYNC_WAIT_CTX_get_all_fds(async->waitctx, NULL, &numfds);
+    assert(numfds == 1);
+    ASYNC_WAIT_CTX_get_all_fds(async->waitctx, fds, &numfds);
+    return fds[0];
+}
+
+static int do_sign_async_job(void *_async)
+{
+    struct async_sign_ctx *async = *(struct async_sign_ctx **)_async;
+    return EVP_DigestSignFinal(async->ctx, async->sig, &async->siglen);
+}
+
+static int do_sign_async(ptls_buffer_t *outbuf, ptls_async_job_t **_async)
+{
+    struct async_sign_ctx *async = (void *)*_async;
+    int ret;
+
+    switch (ASYNC_start_job(&async->job, async->waitctx, &ret, do_sign_async_job, &async, sizeof(async))) {
+    case ASYNC_PAUSE:
+        return PTLS_ERROR_ASYNC_OPERATION; // async operation inflight; bail out without getting rid of async context
+    case ASYNC_ERR:
+        ret = PTLS_ERROR_LIBRARY;
+        break;
+    case ASYNC_NO_JOBS:
+        ret = PTLS_ERROR_LIBRARY;
+        break;
+    case ASYNC_FINISH:
+        async->job = NULL;
+        ptls_buffer_pushv(outbuf, async->sig, async->siglen);
+        ret = 0;
+        break;
+    default:
+        ret = PTLS_ERROR_LIBRARY;
+        break;
+    }
+
+Exit:
+    async_sign_ctx_free(&async->super);
+    *_async = NULL;
+    return ret;
+}
+
+#endif
+
 static int do_sign(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *scheme, ptls_buffer_t *outbuf,
-                   ptls_iovec_t input)
+                   ptls_iovec_t input, ptls_async_job_t **async)
 {
     EVP_MD_CTX *ctx = NULL;
     const EVP_MD *md = scheme->scheme_md != NULL ? scheme->scheme_md() : NULL;
@@ -751,6 +849,18 @@
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
+        /* If permitted by the caller (by providing a non-NULL `async` slot), use the asynchronous signing method and return
+         * immediately. */
+#if PTLS_OPENSSL_HAVE_ASYNC
+        if (async != NULL) {
+            if ((*async = async_sign_ctx_new(scheme, ctx, siglen)) == NULL) {
+                ret = PTLS_ERROR_NO_MEMORY;
+                goto Exit;
+            }
+            return do_sign_async(outbuf, async);
+        }
+#endif
+        /* Otherwise, generate signature synchronously. */
         if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0)
             goto Exit;
         if (EVP_DigestSignFinal(ctx, outbuf->base + outbuf->off, &siglen) != 1) {
@@ -1047,13 +1157,22 @@
 #define _sha512_final(ctx, md) SHA512_Final((md), (ctx))
 ptls_define_hash(sha512, SHA512_CTX, SHA512_Init, SHA512_Update, _sha512_final);
 
-static int sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *outbuf,
-                            ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
+static int sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm,
+                            ptls_buffer_t *outbuf, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
 {
     ptls_openssl_sign_certificate_t *self = (ptls_openssl_sign_certificate_t *)_self;
     const struct st_ptls_openssl_signature_scheme_t *scheme;
 
-    /* select the algorithm (driven by server-side preference of `self->schemes`) */
+    /* Just resume the asynchronous operation, if one is in flight. */
+#if PTLS_OPENSSL_HAVE_ASYNC
+    if (async != NULL && *async != NULL) {
+        struct async_sign_ctx *sign_ctx = (struct async_sign_ctx *)(*async);
+        *selected_algorithm = sign_ctx->scheme->scheme_id;
+        return do_sign_async(outbuf, async);
+    }
+#endif
+
+    /* Select the algorithm (driven by server-side preference of `self->schemes`), or return failure if none found. */
     for (scheme = self->schemes; scheme->scheme_id != UINT16_MAX; ++scheme) {
         size_t i;
         for (i = 0; i != num_algorithms; ++i)
@@ -1064,7 +1183,14 @@
 
 Found:
     *selected_algorithm = scheme->scheme_id;
-    return do_sign(self->key, scheme, outbuf, input);
+#if PTLS_OPENSSL_HAVE_ASYNC
+    if (!self->async && async != NULL) {
+        /* indicate to `do_sign` that async mode is disabled for this operation */
+        assert(*async == NULL);
+        async = NULL;
+    }
+#endif
+    return do_sign(self->key, scheme, outbuf, input, async);
 }
 
 static X509 *to_x509(ptls_iovec_t vec)
@@ -1154,7 +1280,7 @@
 
 int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key)
 {
-    *self = (ptls_openssl_sign_certificate_t){{sign_certificate}};
+    *self = (ptls_openssl_sign_certificate_t){.super = {sign_certificate}, .async = 1};
 
     if ((self->schemes = lookup_signature_schemes(key)) == NULL)
         return PTLS_ERROR_INCOMPATIBLE_KEY;
diff --git a/lib/picotls.c b/lib/picotls.c
index aebd013..eb77e67 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -167,6 +167,7 @@
         PTLS_STATE_CLIENT_EXPECT_FINISHED,
         PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO,
         PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO,
+        PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY,
         PTLS_STATE_SERVER_EXPECT_CERTIFICATE,
         PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY,
         /* ptls_send can be called if the state is below here */
@@ -250,6 +251,8 @@
         struct {
             uint8_t pending_traffic_secret[PTLS_MAX_DIGEST_SIZE];
             uint32_t early_data_skipped_bytes; /* if not UINT32_MAX, the server is skipping early data */
+            unsigned can_send_session_ticket : 1;
+            ptls_async_job_t *async_job;
         } server;
     };
     /**
@@ -380,6 +383,8 @@
 
 static ptls_aead_context_t *new_aead(ptls_aead_algorithm_t *aead, ptls_hash_algorithm_t *hash, int is_enc, const void *secret,
                                      ptls_iovec_t hash_value, const char *label_prefix);
+static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify,
+                                   struct st_ptls_signature_algorithms_t *signature_algorithms);
 
 static int is_supported_version(uint16_t v)
 {
@@ -2723,10 +2728,9 @@
     return ret;
 }
 
-static int send_certificate_and_certificate_verify(ptls_t *tls, ptls_message_emitter_t *emitter,
-                                                   struct st_ptls_signature_algorithms_t *signature_algorithms,
-                                                   ptls_iovec_t context, const char *context_string, int push_status_request,
-                                                   const uint16_t *compress_algos, size_t num_compress_algos)
+static int send_certificate(ptls_t *tls, ptls_message_emitter_t *emitter,
+                            struct st_ptls_signature_algorithms_t *signature_algorithms, ptls_iovec_t context,
+                            int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos)
 {
     int ret;
 
@@ -2751,27 +2755,47 @@
         }
     }
 
-    /* build and send CertificateVerify */
-    if (tls->ctx->sign_certificate != NULL) {
-        ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, {
-            ptls_buffer_t *sendbuf = emitter->buf;
-            size_t algo_off = sendbuf->off;
-            ptls_buffer_push16(sendbuf, 0); /* filled in later */
-            ptls_buffer_push_block(sendbuf, 2, {
-                uint16_t algo;
-                uint8_t data[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE];
-                size_t datalen = build_certificate_verify_signdata(data, tls->key_schedule, context_string);
-                if ((ret = tls->ctx->sign_certificate->cb(tls->ctx->sign_certificate, tls, &algo, sendbuf,
-                                                          ptls_iovec_init(data, datalen), signature_algorithms->list,
-                                                          signature_algorithms->count)) != 0) {
-                    goto Exit;
-                }
-                sendbuf->base[algo_off] = (uint8_t)(algo >> 8);
-                sendbuf->base[algo_off + 1] = (uint8_t)algo;
-            });
-        });
-    }
+Exit:
+    return ret;
+}
 
+static int send_certificate_verify(ptls_t *tls, ptls_message_emitter_t *emitter,
+                                   struct st_ptls_signature_algorithms_t *signature_algorithms, const char *context_string)
+{
+    size_t start_off = emitter->buf->off;
+    int ret;
+
+    if (tls->ctx->sign_certificate == NULL)
+        return 0;
+    /* build and send CertificateVerify */
+    ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, {
+        ptls_buffer_t *sendbuf = emitter->buf;
+        size_t algo_off = sendbuf->off;
+        ptls_buffer_push16(sendbuf, 0); /* filled in later */
+        ptls_buffer_push_block(sendbuf, 2, {
+            uint16_t algo;
+            uint8_t data[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE];
+            size_t datalen = build_certificate_verify_signdata(data, tls->key_schedule, context_string);
+            if ((ret = tls->ctx->sign_certificate->cb(
+                     tls->ctx->sign_certificate, tls, tls->is_server ? &tls->server.async_job : NULL, &algo, sendbuf,
+                     ptls_iovec_init(data, datalen), signature_algorithms != NULL ? signature_algorithms->list : NULL,
+                     signature_algorithms != NULL ? signature_algorithms->count : 0)) != 0) {
+                if (ret == PTLS_ERROR_ASYNC_OPERATION) {
+                    assert(tls->is_server || !"async operation only supported on the server-side");
+                    assert(tls->server.async_job != NULL);
+                    /* Reset the output to the end of the previous handshake message. CertificateVerify will be rebuilt when the
+                     * async operation completes. */
+                    emitter->buf->off = start_off;
+                } else {
+                    assert(tls->server.async_job == NULL);
+                }
+                goto Exit;
+            }
+            assert(tls->server.async_job == NULL);
+            sendbuf->base[algo_off] = (uint8_t)(algo >> 8);
+            sendbuf->base[algo_off + 1] = (uint8_t)algo;
+        });
+    });
 Exit:
     return ret;
 }
@@ -3027,9 +3051,10 @@
             ret = PTLS_ALERT_ILLEGAL_PARAMETER;
             goto Exit;
         }
-        ret = send_certificate_and_certificate_verify(tls, emitter, &tls->client.certificate_request.signature_algorithms,
-                                                      tls->client.certificate_request.context,
-                                                      PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING, 0, NULL, 0);
+        if ((ret = send_certificate(tls, emitter, &tls->client.certificate_request.signature_algorithms,
+                                    tls->client.certificate_request.context, 0, NULL, 0)) == 0)
+            ret = send_certificate_verify(tls, emitter, &tls->client.certificate_request.signature_algorithms,
+                                          PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING);
         free(tls->client.certificate_request.context.base);
         tls->client.certificate_request.context = ptls_iovec_init(NULL, 0);
         if (ret != 0)
@@ -4050,6 +4075,7 @@
             properties->server.selected_psk_binder.len = selected->len;
         }
     }
+    tls->server.can_send_session_ticket = ch->psk.ke_modes != 0;
 
     if (accept_early_data && tls->ctx->max_early_data_size != 0 && psk_index == 0) {
         if ((tls->pending_handshake_secret = malloc(PTLS_MAX_DIGEST_SIZE)) == NULL) {
@@ -4149,6 +4175,7 @@
     });
 
     if (mode == HANDSHAKE_MODE_FULL) {
+        /* send certificate request if client authentication is activated */
         if (tls->ctx->require_client_authentication) {
             ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_REQUEST, {
                 /* certificate_request_context, this field SHALL be zero length, unless the certificate
@@ -4164,11 +4191,51 @@
                     });
                 });
             });
+
+            if (ret != 0) {
+                goto Exit;
+            }
         }
-        if ((ret = send_certificate_and_certificate_verify(tls, emitter, &ch->signature_algorithms, ptls_iovec_init(NULL, 0),
-                                                           PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING, ch->status_request,
-                                                           ch->cert_compression_algos.list, ch->cert_compression_algos.count)) != 0)
+
+        /* send certificate */
+        if ((ret = send_certificate(tls, emitter, &ch->signature_algorithms, ptls_iovec_init(NULL, 0), ch->status_request,
+                                    ch->cert_compression_algos.list, ch->cert_compression_algos.count)) != 0)
             goto Exit;
+        /* send certificateverify, finished, and complete the handshake */
+        if ((ret = server_finish_handshake(tls, emitter, 1, &ch->signature_algorithms)) != 0)
+            goto Exit;
+    } else {
+        /* send finished, and complete the handshake */
+        if ((ret = server_finish_handshake(tls, emitter, 0, NULL)) != 0)
+            goto Exit;
+    }
+
+Exit:
+    free(pubkey.base);
+    if (ecdh_secret.base != NULL) {
+        ptls_clear_memory(ecdh_secret.base, ecdh_secret.len);
+        free(ecdh_secret.base);
+    }
+    free(ch);
+    return ret;
+
+#undef EMIT_SERVER_HELLO
+#undef EMIT_HELLO_RETRY_REQUEST
+}
+
+static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify,
+                                   struct st_ptls_signature_algorithms_t *signature_algorithms)
+{
+    int ret;
+
+    if (send_cert_verify) {
+        if ((ret = send_certificate_verify(tls, emitter, signature_algorithms, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING)) !=
+            0) {
+            if (ret == PTLS_ERROR_ASYNC_OPERATION) {
+                tls->state = PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY;
+            }
+            goto Exit;
+        }
     }
 
     if ((ret = send_finished(tls, emitter)) != 0)
@@ -4199,7 +4266,7 @@
     }
 
     /* send session ticket if necessary */
-    if (ch->psk.ke_modes != 0 && tls->ctx->ticket_lifetime != 0) {
+    if (tls->server.can_send_session_ticket && tls->ctx->ticket_lifetime != 0) {
         if ((ret = send_session_ticket(tls, emitter)) != 0)
             goto Exit;
     }
@@ -4211,16 +4278,7 @@
     }
 
 Exit:
-    free(pubkey.base);
-    if (ecdh_secret.base != NULL) {
-        ptls_clear_memory(ecdh_secret.base, ecdh_secret.len);
-        free(ecdh_secret.base);
-    }
-    free(ch);
     return ret;
-
-#undef EMIT_SERVER_HELLO
-#undef EMIT_HELLO_RETRY_REQUEST
 }
 
 static int server_handle_end_of_early_data(ptls_t *tls, ptls_iovec_t message)
@@ -4640,16 +4698,16 @@
     free(tls->server_name);
     free(tls->negotiated_protocol);
     if (tls->is_server) {
-        /* nothing to do */
+        if (tls->server.async_job != NULL)
+            tls->server.async_job->destroy_(tls->server.async_job);
     } else {
         if (tls->client.key_share_ctx != NULL)
             tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, 1, NULL, ptls_iovec_init(NULL, 0));
         if (tls->client.certificate_request.context.base != NULL)
             free(tls->client.certificate_request.context.base);
     }
-    if (tls->certificate_verify.cb != NULL) {
+    if (tls->certificate_verify.cb != NULL)
         tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, 0, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0));
-    }
     if (tls->pending_handshake_secret != NULL) {
         ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE);
         free(tls->pending_handshake_secret);
@@ -4671,6 +4729,11 @@
     tls->ctx = ctx;
 }
 
+ptls_async_job_t *ptls_get_async_job(ptls_t *tls)
+{
+    return tls->server.async_job;
+}
+
 ptls_iovec_t ptls_get_client_random(ptls_t *tls)
 {
     return ptls_iovec_init(tls->client_random, PTLS_HELLO_RANDOM_SIZE);
@@ -4987,6 +5050,7 @@
         ret = cb(tls, emitter, ptls_iovec_init(src, mess_len), src_end - src == mess_len, properties);
         switch (ret) {
         case 0:
+        case PTLS_ERROR_ASYNC_OPERATION:
         case PTLS_ERROR_IN_PROGRESS:
             break;
         default:
@@ -5193,6 +5257,8 @@
         assert(tls->ctx->key_exchanges[0] != NULL);
         return send_client_hello(tls, &emitter.super, properties, NULL);
     }
+    case PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY:
+        return server_finish_handshake(tls, &emitter.super, 1, NULL);
     default:
         break;
     }
@@ -5217,6 +5283,7 @@
     case 0:
     case PTLS_ERROR_IN_PROGRESS:
     case PTLS_ERROR_STATELESS_RETRY:
+    case PTLS_ERROR_ASYNC_OPERATION:
         break;
     default:
         /* flush partially written response */
@@ -5805,6 +5872,12 @@
         {sendbuf, &tls->traffic_protection.enc, 0, begin_raw_message, commit_raw_message}, SIZE_MAX, epoch_offsets};
     struct st_ptls_record_t rec = {PTLS_CONTENT_TYPE_HANDSHAKE, 0, inlen, input};
 
+    if (tls->state == PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY) {
+        int ret;
+        if ((ret = server_finish_handshake(tls, &emitter.super, 1, NULL)) != 0)
+            return ret;
+    }
+
     assert(input);
 
     if (ptls_get_read_epoch(tls) != in_epoch)
diff --git a/lib/uecc.c b/lib/uecc.c
index c7164be..5d42b3b 100644
--- a/lib/uecc.c
+++ b/lib/uecc.c
@@ -131,8 +131,8 @@
     return ret;
 }
 
-static int secp256r1sha256_sign(ptls_sign_certificate_t *_self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *outbuf,
-                                ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
+static int secp256r1sha256_sign(ptls_sign_certificate_t *_self, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm,
+                                ptls_buffer_t *outbuf, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
 {
     ptls_minicrypto_secp256r1sha256_sign_certificate_t *self = (ptls_minicrypto_secp256r1sha256_sign_certificate_t *)_self;
     uint8_t hash[32], sig[64];
diff --git a/t/minicrypto.c b/t/minicrypto.c
index c003f55..bf09040 100644
--- a/t/minicrypto.c
+++ b/t/minicrypto.c
@@ -52,7 +52,7 @@
     uECC_make_key(pub, signer.key, uECC_secp256r1());
     ptls_buffer_init(&sigbuf, sigbuf_small, sizeof(sigbuf_small));
 
-    ok(secp256r1sha256_sign(&signer.super, NULL, &selected, &sigbuf, ptls_iovec_init(msg, 32),
+    ok(secp256r1sha256_sign(&signer.super, NULL, NULL, &selected, &sigbuf, ptls_iovec_init(msg, 32),
                             (uint16_t[]){PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256}, 1) == 0);
     ok(selected == PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256);
 
diff --git a/t/openssl.c b/t/openssl.c
index 6fba5dd..32984ac 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -35,9 +35,16 @@
 #endif
 #include "picotls.h"
 #include "picotls/minicrypto.h"
+#include "picotls/openssl.h"
+#if PTLS_OPENSSL_HAVE_ASYNC && PTLS_OPENSSL_HAVE_X25519 && !defined(_WINDOWS)
+#include <sys/select.h>
+#include <sys/time.h>
+#define ASYNC_TESTS 1
+#endif
 #include "../deps/picotest/picotest.h"
 #undef OPENSSL_API_COMPAT
 #include "../lib/openssl.c"
+
 #include "test.h"
 
 #define RSA_PRIVATE_KEY                                                                                                            \
@@ -145,7 +152,7 @@
         uint8_t sigbuf_small[1024];
 
         ptls_buffer_init(&sigbuf, sigbuf_small, sizeof(sigbuf_small));
-        ok(do_sign(key, schemes + i, &sigbuf, ptls_iovec_init(message, strlen(message))) == 0);
+        ok(do_sign(key, schemes + i, &sigbuf, ptls_iovec_init(message, strlen(message)), NULL) == 0);
         EVP_PKEY_up_ref(key);
         ok(verify_sign(key, schemes[i].scheme_id, ptls_iovec_init(message, strlen(message)),
                        ptls_iovec_init(sigbuf.base, sigbuf.off)) == 0);
@@ -328,6 +335,153 @@
     test_hpke(ptls_openssl_hpke_kems, ptls_openssl_hpke_cipher_suites);
 }
 
+#if ASYNC_TESTS
+
+static ENGINE *load_engine(const char *name)
+{
+    ENGINE *e;
+
+    if ((e = ENGINE_by_id("dynamic")) == NULL)
+        return NULL;
+    if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", name, 0) || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
+        ENGINE_free(e);
+        return NULL;
+    }
+
+    return e;
+}
+
+static struct {
+    struct {
+        size_t next_pending;
+        ptls_t *tls;
+        int wait_fd;
+    } conns[10];
+    size_t first_pending;
+} qat;
+
+static void qat_set_pending(size_t index)
+{
+    qat.conns[index].next_pending = qat.first_pending;
+    qat.first_pending = index;
+}
+
+static void many_handshakes(void)
+{
+    ptls_t *client = ptls_new(ctx, 0), *resp_sample_conn = NULL;
+    ptls_buffer_t clientbuf, resp_sample;
+    int ret;
+
+    { /* generate ClientHello that we would be sent to all the server-side objects */
+        ptls_buffer_init(&clientbuf, "", 0);
+        ret = ptls_handshake(client, &clientbuf, NULL, NULL, NULL);
+        ok(ret == PTLS_ERROR_IN_PROGRESS);
+    }
+
+    ptls_buffer_init(&resp_sample, "", 0);
+
+    qat.first_pending = 0;
+    for (size_t i = 0; i < PTLS_ELEMENTSOF(qat.conns); ++i) {
+        qat.conns[i].next_pending = i + 1;
+        qat.conns[i].tls = NULL;
+        qat.conns[i].wait_fd = -1;
+    }
+    qat.conns[PTLS_ELEMENTSOF(qat.conns) - 1].next_pending = SIZE_MAX;
+
+    struct timeval start, end;
+    gettimeofday(&start, NULL);
+
+    static const size_t num_total = 10000;
+    size_t num_issued = 0, num_running = 0;
+    while (1) {
+        while (qat.first_pending != SIZE_MAX) {
+            size_t offending = qat.first_pending;
+            /* detach the offending entry from pending list */
+            qat.first_pending = qat.conns[offending].next_pending;
+            qat.conns[offending].next_pending = SIZE_MAX;
+            /* run the offending entry */
+            if (qat.conns[offending].tls == NULL) {
+                qat.conns[offending].tls = ptls_new(ctx_peer, 1);
+                if (resp_sample_conn == NULL)
+                    resp_sample_conn = qat.conns[offending].tls;
+                ++num_issued;
+                ++num_running;
+            }
+            ptls_buffer_t hsbuf;
+            uint8_t hsbuf_small[8192];
+            ptls_buffer_init(&hsbuf, hsbuf_small, sizeof(hsbuf_small));
+            size_t inlen = ptls_get_cipher(qat.conns[offending].tls) == NULL ? clientbuf.off : 0; /* feed CH only as first flight */
+            int hsret = ptls_handshake(qat.conns[offending].tls, &hsbuf, clientbuf.base, &inlen, NULL);
+            if (resp_sample_conn == qat.conns[offending].tls) {
+                ptls_buffer_pushv(&resp_sample, hsbuf.base, hsbuf.off);
+            }
+            ptls_buffer_dispose(&hsbuf);
+            /* advance the handshake context */
+            switch (hsret) {
+            case 0:
+                if (qat.conns[offending].tls == resp_sample_conn)
+                    resp_sample_conn = (void *)1;
+                ptls_free(qat.conns[offending].tls);
+                qat.conns[offending].tls = NULL;
+                --num_running;
+                if (num_issued < num_total)
+                    qat_set_pending(offending);
+                break;
+            case PTLS_ERROR_ASYNC_OPERATION:
+                qat.conns[offending].wait_fd = ptls_openssl_get_async_fd(qat.conns[offending].tls);
+                assert(qat.conns[offending].wait_fd != -1);
+                break;
+            default:
+                fprintf(stderr, "ptls_handshake returned %d\n", hsret);
+                abort();
+                break;
+            }
+        }
+        if (num_running == 0)
+            break;
+        /* poll for next action */
+        fd_set rfds;
+        FD_ZERO(&rfds);
+        int nfds = 0;
+        for (size_t i = 0; i < PTLS_ELEMENTSOF(qat.conns); ++i) {
+            if (qat.conns[i].wait_fd != -1) {
+                FD_SET(qat.conns[i].wait_fd, &rfds);
+                if (nfds <= qat.conns[i].wait_fd)
+                    nfds = qat.conns[i].wait_fd + 1;
+            }
+        }
+        if (select(nfds, &rfds, NULL, NULL, NULL) > 0) {
+            for (size_t i = 0; i < PTLS_ELEMENTSOF(qat.conns); ++i) {
+                if (qat.conns[i].wait_fd != -1 && FD_ISSET(qat.conns[i].wait_fd, &rfds)) {
+                    qat.conns[i].wait_fd = -1;
+                    qat_set_pending(i);
+                }
+            }
+        }
+    }
+
+    gettimeofday(&end, NULL);
+
+    note("run %zu handshakes in %f seconds", num_total,
+         (end.tv_sec + end.tv_usec / 1000000.) - (start.tv_sec + start.tv_usec / 1000000.));
+
+    clientbuf.off = 0;
+
+    /* confirm that the response looks okay */
+    size_t resplen = resp_sample.off;
+    ok(ptls_handshake(client, &clientbuf, resp_sample.base, &resplen, NULL) == 0);
+
+    ptls_buffer_dispose(&clientbuf);
+    ptls_buffer_dispose(&resp_sample);
+    ptls_free(client);
+
+    return;
+Exit:
+    assert("unreachable");
+}
+
+#endif
+
 int main(int argc, char **argv)
 {
     ptls_openssl_sign_certificate_t openssl_sign_certificate;
@@ -419,6 +573,32 @@
     ctx_peer = &openssl_ctx;
     subtest("minicrypto vs.", test_picotls);
 
+#if ASYNC_TESTS
+    // switch to x25519 / aes128gcmsha256 as we run benchmarks
+    static ptls_key_exchange_algorithm_t *fast_keyex[] = {&ptls_openssl_x25519, NULL}; // use x25519 for speed
+    static ptls_cipher_suite_t *fast_cipher[] = {&ptls_openssl_aes128gcmsha256, NULL};
+    openssl_ctx.key_exchanges = fast_keyex;
+    openssl_ctx.cipher_suites = fast_cipher;
+    ctx = &openssl_ctx;
+    ctx_peer = &openssl_ctx;
+    openssl_sign_certificate.async = 0;
+    subtest("many-handshakes-non-async", many_handshakes);
+    openssl_sign_certificate.async = 0;
+    subtest("many-handshakes-async", many_handshakes);
+    { /* qatengine should be tested at last, because we do not have the code to unload or un-default it */
+        const char *engine_name = "qatengine";
+        ENGINE *qatengine;
+        if ((qatengine = ENGINE_by_id(engine_name)) != NULL || (qatengine = load_engine(engine_name)) != NULL) {
+            ENGINE_set_default_RSA(qatengine);
+            ptls_openssl_dispose_sign_certificate(&openssl_sign_certificate); // reload cert to use qatengine
+            setup_sign_certificate(&openssl_sign_certificate);
+            subtest("many-handshakes-qatengine", many_handshakes);
+        } else {
+            note("%s not found", engine_name);
+        }
+    }
+#endif
+
     esni_private_keys[0]->on_exchange(esni_private_keys, 1, NULL, ptls_iovec_init(NULL, 0));
 
     subtest("hpke", test_all_hpke);
diff --git a/t/picotls.c b/t/picotls.c
index cb21f44..a4a802b 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -78,6 +78,7 @@
 ptls_context_t *ctx, *ctx_peer;
 ptls_verify_certificate_t *verify_certificate;
 struct st_ptls_ffx_test_variants_t ffx_variants[7];
+static unsigned server_sc_callcnt, client_sc_callcnt, async_sc_callcnt;
 
 static ptls_cipher_suite_t *find_cipher(ptls_context_t *ctx, uint16_t id)
 {
@@ -643,6 +644,10 @@
     const char *req = "GET / HTTP/1.0\r\n\r\n";
     const char *resp = "HTTP/1.0 200 OK\r\n\r\nhello world\n";
 
+    client_sc_callcnt = 0;
+    server_sc_callcnt = 0;
+    async_sc_callcnt = 0;
+
     if (check_ch)
         ctx->verify_certificate = verify_certificate;
 
@@ -725,10 +730,13 @@
     ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, &server_hs_prop);
 
     if (require_client_authentication) {
+        /* at the moment, async sign-certificate is not supported in this path, neither on the client-side or the server-side */
         ok(ptls_is_psk_handshake(server) == 0);
         ok(ret == PTLS_ERROR_IN_PROGRESS);
-    } else {
+    } else if (mode == TEST_HANDSHAKE_EARLY_DATA) {
         ok(ret == 0);
+    } else {
+        ok(ret == 0 || ret == PTLS_ERROR_ASYNC_OPERATION);
     }
 
     ok(sbuf.off != 0);
@@ -765,6 +773,21 @@
         cbuf.off = 0;
     }
 
+    while (ret == PTLS_ERROR_ASYNC_OPERATION) {
+        consumed = sbuf.off;
+        ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, NULL);
+        ok(ret == PTLS_ERROR_IN_PROGRESS);
+        ok(consumed == sbuf.off);
+        ok(cbuf.off == 0);
+        sbuf.off = 0;
+        ret = ptls_handshake(server, &sbuf, NULL, NULL, &server_hs_prop);
+    }
+    if (require_client_authentication) {
+        ok(ret == PTLS_ERROR_IN_PROGRESS);
+    } else {
+        ok(ret == 0);
+    }
+
     consumed = sbuf.off;
     ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, NULL);
     ok(ret == 0);
@@ -882,58 +905,86 @@
 }
 
 static ptls_sign_certificate_t *sc_orig;
-size_t sc_callcnt;
 
-static int sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *output,
-                            ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
+static int sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm,
+                            ptls_buffer_t *output, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
 {
-    ++sc_callcnt;
-    return sc_orig->cb(sc_orig, tls, selected_algorithm, output, input, algorithms, num_algorithms);
+    ++*(ptls_is_server(tls) ? &server_sc_callcnt : &client_sc_callcnt);
+    return sc_orig->cb(sc_orig, tls, async, selected_algorithm, output, input, algorithms, num_algorithms);
+}
+
+static int async_sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, ptls_async_job_t **async,
+                                  uint16_t *selected_algorithm, ptls_buffer_t *output, ptls_iovec_t input,
+                                  const uint16_t *algorithms, size_t num_algorithms)
+{
+    static struct {
+        ptls_async_job_t super;
+        uint16_t selected_algorithm;
+    } async_ctx;
+
+    if (async != NULL) {
+        if (*async == NULL) {
+            /* first invocation, make a fake call to the backend and obtain the algorithm, return it, but not the signature */
+            ptls_buffer_t fakebuf;
+            ptls_buffer_init(&fakebuf, "", 0);
+            int ret = sign_certificate(self, tls, NULL, selected_algorithm, &fakebuf, input, algorithms, num_algorithms);
+            assert(ret == 0);
+            ptls_buffer_dispose(&fakebuf);
+            async_ctx.super.destroy_ = (void (*)(ptls_async_job_t *))0xdeadbeef;
+            async_ctx.selected_algorithm = *selected_algorithm;
+            *async = &async_ctx.super;
+            --server_sc_callcnt;
+            ++async_sc_callcnt;
+            return PTLS_ERROR_ASYNC_OPERATION;
+        } else {
+            /* second invocation, restore algorithm, and delegate the call */
+            assert(*async == &async_ctx.super);
+            assert(algorithms == NULL);
+            algorithms = &async_ctx.selected_algorithm;
+            num_algorithms = 1;
+            *async = NULL;
+        }
+    }
+
+    return sign_certificate(self, tls, NULL, selected_algorithm, output, input, algorithms, num_algorithms);
 }
 
 static ptls_sign_certificate_t *second_sc_orig;
 
-static int second_sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *output,
-                                   ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
+static int second_sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, ptls_async_job_t **async,
+                                   uint16_t *selected_algorithm, ptls_buffer_t *output, ptls_iovec_t input,
+                                   const uint16_t *algorithms, size_t num_algorithms)
 {
-    ++sc_callcnt;
-    return second_sc_orig->cb(second_sc_orig, tls, selected_algorithm, output, input, algorithms, num_algorithms);
+    ++*(ptls_is_server(tls) ? &server_sc_callcnt : &client_sc_callcnt);
+    return second_sc_orig->cb(second_sc_orig, tls, async, selected_algorithm, output, input, algorithms, num_algorithms);
 }
 
-static void test_full_handshake_impl(int require_client_authentication)
+static void test_full_handshake_impl(int require_client_authentication, int is_async)
 {
-    sc_callcnt = 0;
+    test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 0, require_client_authentication);
+    ok(server_sc_callcnt == 1);
+    ok(async_sc_callcnt == is_async);
+    ok(client_sc_callcnt == require_client_authentication);
 
     test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 2);
-    } else {
-        ok(sc_callcnt == 1);
-    }
-
-    test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 4);
-    } else {
-        ok(sc_callcnt == 2);
-    }
+    ok(server_sc_callcnt == 1);
+    ok(async_sc_callcnt == is_async);
+    ok(client_sc_callcnt == require_client_authentication);
 
     test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 1, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 6);
-    } else {
-        ok(sc_callcnt == 3);
-    }
+    ok(server_sc_callcnt == 1);
+    ok(async_sc_callcnt == is_async);
+    ok(client_sc_callcnt == require_client_authentication);
 }
 
 static void test_full_handshake(void)
 {
-    test_full_handshake_impl(0);
+    test_full_handshake_impl(0, 0);
 }
 
 static void test_full_handshake_with_client_authentication(void)
 {
-    test_full_handshake_impl(1);
+    test_full_handshake_impl(1, 0);
 }
 
 static void test_key_update(void)
@@ -943,16 +994,14 @@
 
 static void test_hrr_handshake(void)
 {
-    sc_callcnt = 0;
     test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR, 0, 0, 0);
-    ok(sc_callcnt == 1);
+    ok(server_sc_callcnt == 1);
 }
 
 static void test_hrr_stateless_handshake(void)
 {
-    sc_callcnt = 0;
     test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR_STATELESS, 0, 0, 0);
-    ok(sc_callcnt == 1);
+    ok(server_sc_callcnt == 1);
 }
 
 static int on_copy_ticket(ptls_encrypt_ticket_t *self, ptls_t *tls, int is_encrypt, ptls_buffer_t *dst, ptls_iovec_t src)
@@ -1003,44 +1052,31 @@
     ctx_peer->encrypt_ticket = &et;
     ctx->save_ticket = &st;
 
-    sc_callcnt = 0;
     test_handshake(saved_ticket, different_preferred_key_share ? TEST_HANDSHAKE_2RTT : TEST_HANDSHAKE_1RTT, 1, 0, 0);
-    ok(sc_callcnt == 1);
+    ok(server_sc_callcnt == 1);
     ok(saved_ticket.base != NULL);
 
     /* psk using saved ticket */
     test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 3);
-    } else {
-        ok(sc_callcnt == 1);
-    }
+    ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
+    ok(client_sc_callcnt == require_client_authentication);
 
     /* 0-rtt psk using saved ticket */
     test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 5);
-    } else {
-        ok(sc_callcnt == 1);
-    }
+    ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
+    ok(client_sc_callcnt == require_client_authentication);
 
     ctx->require_dhe_on_psk = 1;
 
     /* psk-dhe using saved ticket */
     test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 7);
-    } else {
-        ok(sc_callcnt == 1);
-    }
+    ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
+    ok(client_sc_callcnt == require_client_authentication);
 
     /* 0-rtt psk-dhe using saved ticket */
     test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication);
-    if (require_client_authentication) {
-        ok(sc_callcnt == 9);
-    } else {
-        ok(sc_callcnt == 1);
-    }
+    ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
+    ok(client_sc_callcnt == require_client_authentication);
 
     ctx->require_dhe_on_psk = 0;
     ctx_peer->ticket_lifetime = 0;
@@ -1067,6 +1103,18 @@
     test_resumption_impl(0, 1);
 }
 
+static void test_async_sign_certificate(void)
+{
+    assert(ctx_peer->sign_certificate->cb == sign_certificate);
+
+    ptls_sign_certificate_t async_sc = {async_sign_certificate}, *orig_sc = ctx_peer->sign_certificate;
+    ctx_peer->sign_certificate = &async_sc;
+
+    test_full_handshake_impl(0, 1);
+
+    ctx_peer->sign_certificate = orig_sc;
+}
+
 static void test_enforce_retry(int use_cookie)
 {
     ptls_t *client, *server;
@@ -1517,6 +1565,8 @@
     subtest("resumption-different-preferred-key-share", test_resumption_different_preferred_key_share);
     subtest("resumption-with-client-authentication", test_resumption_with_client_authentication);
 
+    subtest("async-sign-certificate", test_async_sign_certificate);
+
     subtest("enforce-retry-stateful", test_enforce_retry_stateful);
     subtest("enforce-retry-stateless", test_enforce_retry_stateless);