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);