diff --git a/include/picotls.h b/include/picotls.h index 0c72fa2bc..f401d21fd 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -191,6 +191,7 @@ extern "C" { #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) @@ -604,10 +605,17 @@ PTLS_CALLBACK_TYPE(int, on_client_hello, ptls_t *tls, ptls_on_client_hello_param 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. + * When gerenating CertificateVerify, the core calls the callback to sign the handshake context using the certificate. This callback + * may return PTLS_ERROR_ASYNC_OPERATION, and signal the application outside of picotls when the signature has been generated. At + * that point, the application should call `ptls_handshake`, which in turn would invoke this callback once again. The callback then + * fills `*selected_algorithm` and `output` with the signature being generated. Note that `algorithms` and `num_algorithms` are + * provided only when the callback is called for the first time. The callback can store arbitrary pointer specific to each signature + * generation in `*sign_ctx`. + * When `ptls_t` is disposed of while the async operation is in flight, `*cancel_cb` will be invoked. The backend should abort the + * calculation and free any temporary data allocated for that calculation. */ -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); +PTLS_CALLBACK_TYPE(int, sign_certificate, ptls_t *tls, void (**cancel_cb)(void *sign_ctx), void **sign_certificate_ctx, + 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 @@ -1157,6 +1165,10 @@ ptls_context_t *ptls_get_context(ptls_t *tls); * updates the context of a connection. Can be called from `on_client_hello` callback. */ void ptls_set_context(ptls_t *tls, ptls_context_t *ctx); +/** + * get the signature context + */ +void *ptls_get_sign_context(ptls_t *tls); /** * returns the client-random */ diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h index 867f5d54c..9f58b465a 100644 --- a/include/picotls/openssl.h +++ b/include/picotls/openssl.h @@ -39,6 +39,12 @@ extern "C" { #endif #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100010L && !defined(LIBRESSL_VERSION_NUMBER) +#if !defined(OPENSSL_NO_ASYNC) +#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 @@ -91,6 +97,9 @@ void ptls_openssl_random_bytes(void *buf, size_t len); * constructs a key exchange context. pkey's reference count is incremented. */ int ptls_openssl_create_key_exchange(ptls_key_exchange_context_t **ctx, EVP_PKEY *pkey); +#ifdef PTLS_OPENSSL_HAVE_ASYNC +int ptls_openssl_get_async_fd(ptls_t *ptls); +#endif struct st_ptls_openssl_signature_scheme_t { uint16_t scheme_id; @@ -101,6 +110,10 @@ typedef struct st_ptls_openssl_sign_certificate_t { ptls_sign_certificate_t super; EVP_PKEY *key; const struct st_ptls_openssl_signature_scheme_t *schemes; /* terminated by .scheme_id == UINT16_MAX */ + /** + * boolean indicating if signing should be asynchronous + */ + 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 b83dc366a..08084f3db 100644 --- a/lib/openssl.c +++ b/lib/openssl.c @@ -45,6 +45,10 @@ #include "picotls.h" #include "picotls/openssl.h" +#ifdef PTLS_OPENSSL_HAVE_ASYNC +#include +#endif + #ifdef _WINDOWS #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS @@ -694,75 +698,208 @@ int ptls_openssl_create_key_exchange(ptls_key_exchange_context_t **ctx, EVP_PKEY } } +struct sign_ctx { + const struct st_ptls_openssl_signature_scheme_t *scheme; + EVP_MD_CTX *ctx; +#ifdef PTLS_OPENSSL_HAVE_ASYNC + ASYNC_WAIT_CTX *waitctx; + ASYNC_JOB *job; +#endif + size_t siglen; + // must be last, see `sign_ctx_alloc` + uint8_t sig[0]; +}; + +static struct sign_ctx *sign_ctx_alloc(size_t siglen) +{ + // the last member of `struct sign_ctx` is a buffer to store `siglen` bytes + struct sign_ctx *sign_ctx = malloc(sizeof(*sign_ctx) + siglen); + memset(sign_ctx, 0, sizeof(*sign_ctx) + siglen); + sign_ctx->siglen = siglen; +#ifdef PTLS_OPENSSL_HAVE_ASYNC + sign_ctx->waitctx = ASYNC_WAIT_CTX_new(); +#endif + + return sign_ctx; +} + +static void sign_ctx_destroy(struct sign_ctx *sign_ctx) +{ + if (sign_ctx == NULL) return; + + if (sign_ctx->ctx != NULL) + EVP_MD_CTX_destroy(sign_ctx->ctx); +#ifdef PTLS_OPENSSL_HAVE_ASYNC + if (sign_ctx->job != NULL) { + int _ret; + // resume the job to free resources + ASYNC_start_job(&sign_ctx->job, sign_ctx->waitctx, &_ret, NULL, NULL, 0); + } + if (sign_ctx->waitctx != NULL) + ASYNC_WAIT_CTX_free(sign_ctx->waitctx); +#endif + free(sign_ctx); +} + +#ifdef PTLS_OPENSSL_HAVE_ASYNC +int ptls_openssl_get_async_fd(ptls_t *ptls) +{ + int fds[1]; + size_t numfds; + struct sign_ctx *args = ptls_get_sign_context(ptls); + ASYNC_WAIT_CTX_get_all_fds(args->waitctx, NULL, &numfds); + assert(numfds == 1); + ASYNC_WAIT_CTX_get_all_fds(args->waitctx, fds, &numfds); + return fds[0]; +} +#endif + +static void async_sign_cancel(void *vargs) +{ + struct sign_ctx *args = (struct sign_ctx *)vargs; + sign_ctx_destroy(args); +} + +static int do_sign_final(void *vargs) +{ + struct sign_ctx *args = *(struct sign_ctx **)vargs; + return EVP_DigestSignFinal(args->ctx, args->sig, &args->siglen); +} + 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, void (**cancel_cb)(void *sign_ctx), void **sign_ctx, int is_async) { - EVP_MD_CTX *ctx = NULL; const EVP_MD *md = scheme->scheme_md != NULL ? scheme->scheme_md() : NULL; - EVP_PKEY_CTX *pkey_ctx; - size_t siglen; + int pkey_id = EVP_PKEY_id(key); int ret; - if ((ctx = EVP_MD_CTX_create()) == NULL) { - ret = PTLS_ERROR_NO_MEMORY; - goto Exit; - } - - if (EVP_DigestSignInit(ctx, &pkey_ctx, md, NULL, key) != 1) { - ret = PTLS_ERROR_LIBRARY; - goto Exit; - } + struct sign_ctx *args = NULL; + EVP_MD_CTX *ctx = NULL; + if (sign_ctx != NULL && *sign_ctx != NULL) { + args = *sign_ctx; + } else { + size_t siglen; + EVP_PKEY_CTX *pkey_ctx; -#if PTLS_OPENSSL_HAVE_ED25519 - if (EVP_PKEY_id(key) == EVP_PKEY_ED25519) { - /* ED25519 requires the use of the all-at-once function that appeared in OpenSSL 1.1.1, hence different path */ - if (EVP_DigestSign(ctx, NULL, &siglen, input.base, input.len) != 1) { - ret = PTLS_ERROR_LIBRARY; + if ((ctx = EVP_MD_CTX_create()) == NULL) { + ret = PTLS_ERROR_NO_MEMORY; goto Exit; } - if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0) - goto Exit; - if (EVP_DigestSign(ctx, outbuf->base + outbuf->off, &siglen, input.base, input.len) != 1) { + + if (EVP_DigestSignInit(ctx, &pkey_ctx, md, NULL, key) != 1) { ret = PTLS_ERROR_LIBRARY; goto Exit; } - } else -#endif - { - if (EVP_PKEY_id(key) == EVP_PKEY_RSA) { - if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) { + +#if PTLS_OPENSSL_HAVE_ED25519 + if (pkey_id == EVP_PKEY_ED25519) { + /* ED25519 requires the use of the all-at-once function that appeared in OpenSSL 1.1.1, hence different path */ + if (EVP_DigestSign(ctx, NULL, &siglen, input.base, input.len) != 1) { ret = PTLS_ERROR_LIBRARY; goto Exit; } - if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) { + if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0) + goto Exit; + if (EVP_DigestSign(ctx, outbuf->base + outbuf->off, &siglen, input.base, input.len) != 1) { ret = PTLS_ERROR_LIBRARY; goto Exit; } - if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, md) != 1) { + } else +#endif + { + if (pkey_id == EVP_PKEY_RSA) { + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, md) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + } + if (EVP_DigestSignUpdate(ctx, input.base, input.len) != 1) { ret = PTLS_ERROR_LIBRARY; goto Exit; } + + if (EVP_DigestSignFinal(ctx, NULL, &siglen) != 1) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + + args = sign_ctx_alloc(siglen); + args->ctx = ctx; + ctx = NULL; + args->scheme = scheme; + if (sign_ctx != NULL) + *sign_ctx = args; } - if (EVP_DigestSignUpdate(ctx, input.base, input.len) != 1) { - ret = PTLS_ERROR_LIBRARY; - goto Exit; - } - if (EVP_DigestSignFinal(ctx, NULL, &siglen) != 1) { - ret = PTLS_ERROR_LIBRARY; - goto Exit; + } + +#if PTLS_OPENSSL_HAVE_ED25519 + if (pkey_id == EVP_PKEY_ED25519) { + // signing is complete as ED25519 is one-shot + } else +#endif + { + if (is_async) { +#ifdef PTLS_OPENSSL_HAVE_ASYNC + if (ASYNC_get_current_job() == NULL) { + // start async sign + switch (ASYNC_start_job(&args->job, args->waitctx, &ret, do_sign_final, &args, sizeof(struct sign_ctx*))) + { + case ASYNC_ERR: + ret = PTLS_ERROR_LIBRARY; + goto Exit; + case ASYNC_PAUSE: { + ret = PTLS_ERROR_ASYNC_OPERATION; + assert(*cancel_cb == NULL); + *cancel_cb = async_sign_cancel; + return ret; + } + case ASYNC_NO_JOBS: + ret = PTLS_ERROR_LIBRARY; + goto Exit; + case ASYNC_FINISH: { + args->job = NULL; + if (ptls_buffer__do_pushv(outbuf, args->sig, args->siglen) != 0) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + break; + } + default: + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } } - if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0) - goto Exit; - if (EVP_DigestSignFinal(ctx, outbuf->base + outbuf->off, &siglen) != 1) { - ret = PTLS_ERROR_LIBRARY; - goto Exit; +#else + do_sign_final(&args); + if (ptls_buffer__do_pushv(outbuf, args->sig, args->siglen) != 0) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } +#endif + } else { + do_sign_final(&args); + if (ptls_buffer__do_pushv(outbuf, args->sig, args->siglen) != 0) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } } } - outbuf->off += siglen; - ret = 0; Exit: + if (sign_ctx != NULL) + *sign_ctx = NULL; + if (cancel_cb != NULL) + *cancel_cb = NULL; + sign_ctx_destroy(args); if (ctx != NULL) EVP_MD_CTX_destroy(ctx); return ret; @@ -1044,24 +1181,55 @@ ptls_define_hash(sha256, SHA256_CTX, SHA256_Init, SHA256_Update, _sha256_final); #define _sha384_final(ctx, md) SHA384_Final((md), (ctx)) ptls_define_hash(sha384, SHA512_CTX, SHA384_Init, SHA384_Update, _sha384_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 const struct st_ptls_openssl_signature_scheme_t *match_scheme(const struct st_ptls_openssl_signature_scheme_t *schemes, + 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`) */ - for (scheme = self->schemes; scheme->scheme_id != UINT16_MAX; ++scheme) { + for (scheme = schemes; scheme->scheme_id != UINT16_MAX; ++scheme) { size_t i; - for (i = 0; i != num_algorithms; ++i) - if (algorithms[i] == scheme->scheme_id) - goto Found; + for (i = 0; i != num_algorithms; ++i) { + if (algorithms[i] == scheme->scheme_id) { + return scheme; + } + } } + return scheme; +} +static int sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, void (**cancel_cb)(void *sign_ctx), void **sign_ctx, + 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; + int is_async = self->async == 1; + + if (ptls_is_server(tls)) { + assert(sign_ctx != NULL); + // server signing is asynchronous + struct sign_ctx *args = *sign_ctx; + if (args == NULL) { + // first invocation, get scheme + scheme = match_scheme(self->schemes, algorithms, num_algorithms); + goto Exit; + } else { + // second invocation + // algorithms should be NULL, re-use cached scheme + assert(algorithms == NULL); + scheme = args->scheme; + goto Exit; + } + } else { + assert(sign_ctx == NULL); + scheme = match_scheme(self->schemes, algorithms, num_algorithms); + goto Exit; + } + return PTLS_ALERT_HANDSHAKE_FAILURE; -Found: +Exit: *selected_algorithm = scheme->scheme_id; - return do_sign(self->key, scheme, outbuf, input); + return do_sign(self->key, scheme, outbuf, input, cancel_cb, sign_ctx, is_async); } static X509 *to_x509(ptls_iovec_t vec) @@ -1152,6 +1320,7 @@ static int verify_sign(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_ int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key) { *self = (ptls_openssl_sign_certificate_t){{sign_certificate}}; + self->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 7dd75ba8d..79623d40a 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -167,6 +167,7 @@ struct st_ptls_t { 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,11 @@ struct st_ptls_t { 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; + struct { + void (*cancel_cb)(void *sign_certificate_ctx); + void *sign_certificate_ctx; + } sign_certificate; } server; }; /** @@ -383,6 +389,8 @@ static int hkdf_expand_label(ptls_hash_algorithm_t *algo, void *output, size_t o ptls_iovec_t hash_value, const char *label_prefix); 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) { @@ -2700,10 +2708,10 @@ static int default_emit_certificate_cb(ptls_emit_certificate_t *_self, ptls_t *t 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; @@ -2728,27 +2736,46 @@ static int send_certificate_and_certificate_verify(ptls_t *tls, ptls_message_emi } } +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 */ - 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; + 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.sign_certificate.cancel_cb : NULL, + tls->is_server ? &tls->server.sign_certificate.sign_certificate_ctx : 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"); + /* 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; } - sendbuf->base[algo_off] = (uint8_t)(algo >> 8); - sendbuf->base[algo_off + 1] = (uint8_t)algo; - }); + goto Exit; + } + sendbuf->base[algo_off] = (uint8_t)(algo >> 8); + sendbuf->base[algo_off + 1] = (uint8_t)algo; }); - } - + }); Exit: return ret; } @@ -3004,9 +3031,10 @@ static int client_handle_finished(ptls_t *tls, ptls_message_emitter_t *emitter, 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) @@ -4031,6 +4059,8 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl 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) { @@ -4130,6 +4160,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl }); 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 @@ -4145,11 +4176,50 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl }); }); }); + + 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) @@ -4180,7 +4250,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } /* send session ticket if necessary */ - if (ch->psk.ke_modes != 0 && tls->ctx->ticket_lifetime != 0) { + if (tls->server.can_send_session_ticket != 0 && tls->ctx->ticket_lifetime != 0) { if ((ret = send_session_ticket(tls, emitter)) != 0) goto Exit; } @@ -4192,16 +4262,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } 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) @@ -4427,6 +4488,9 @@ void ptls_free(ptls_t *tls) 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->server.sign_certificate.cancel_cb != NULL) { + tls->server.sign_certificate.cancel_cb(tls->server.sign_certificate.sign_certificate_ctx); + } if (tls->pending_handshake_secret != NULL) { ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE); free(tls->pending_handshake_secret); @@ -4448,6 +4512,12 @@ void ptls_set_context(ptls_t *tls, ptls_context_t *ctx) tls->ctx = ctx; } +void *ptls_get_sign_context(ptls_t *tls) +{ + assert(tls->is_server); + return tls->server.sign_certificate.sign_certificate_ctx; +} + ptls_iovec_t ptls_get_client_random(ptls_t *tls) { return ptls_iovec_init(tls->client_random, PTLS_HELLO_RANDOM_SIZE); @@ -4746,6 +4816,7 @@ static int handle_handshake_record(ptls_t *tls, 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: @@ -4887,6 +4958,8 @@ int ptls_handshake(ptls_t *tls, ptls_buffer_t *_sendbuf, const void *input, size 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; } @@ -4911,6 +4984,7 @@ int ptls_handshake(ptls_t *tls, ptls_buffer_t *_sendbuf, const void *input, size case 0: case PTLS_ERROR_IN_PROGRESS: case PTLS_ERROR_STATELESS_RETRY: + case PTLS_ERROR_ASYNC_OPERATION: break; default: /* flush partially written response */ @@ -5459,6 +5533,12 @@ int ptls_server_handle_message(ptls_t *tls, ptls_buffer_t *sendbuf, size_t epoch {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 c7164be6f..0bf6d16ef 100644 --- a/lib/uecc.c +++ b/lib/uecc.c @@ -131,7 +131,7 @@ static int secp256r1_key_exchange(ptls_key_exchange_algorithm_t *algo, ptls_iove return ret; } -static int secp256r1sha256_sign(ptls_sign_certificate_t *_self, ptls_t *tls, uint16_t *selected_algorithm, ptls_buffer_t *outbuf, +static int secp256r1sha256_sign(ptls_sign_certificate_t *_self, ptls_t *tls, void **sign_ctx, 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; diff --git a/t/minicrypto.c b/t/minicrypto.c index c003f5568..bf0904090 100644 --- a/t/minicrypto.c +++ b/t/minicrypto.c @@ -52,7 +52,7 @@ static void test_secp256r1_sign(void) 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 fd7a3de85..05742d764 100644 --- a/t/openssl.c +++ b/t/openssl.c @@ -138,18 +138,21 @@ static void test_key_exchanges(void) static void test_sign_verify(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *schemes) { for (size_t i = 0; schemes[i].scheme_id != UINT16_MAX; ++i) { + struct sign_ctx *args = malloc(sizeof(*args)); + memset(args, 0, sizeof(*args)); note("scheme 0x%04x", schemes[i].scheme_id); const void *message = "hello world"; ptls_buffer_t sigbuf; 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)), (void**)&args) == 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); ptls_buffer_dispose(&sigbuf); + free(args); } } diff --git a/t/picotls.c b/t/picotls.c index a4880483b..61d51f253 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -78,6 +78,7 @@ static void test_select_cipher(void) 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 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int 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 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int 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 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int 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,84 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int } 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, void (**cancel_cb)(void *sign_ctx), void **sign_ctx, + uint16_t *selected_algorithm, ptls_buffer_t *output, ptls_iovec_t input, const uint16_t *algorithms, + size_t num_algorithms) +{ + ++*(ptls_is_server(tls) ? &server_sc_callcnt : &client_sc_callcnt); + return sc_orig->cb(sc_orig, tls, cancel_cb, sign_ctx, selected_algorithm, output, input, algorithms, num_algorithms); +} + +static int async_sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, void (**cancel_cb)(void *sign_ctx), void **sign_ctx, + 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); + static void *inner_sign_ctx = NULL; + if (!ptls_is_server(tls)) { + /* do it synchronously, as async mode is only supported on the server-side */ + } + else if (*sign_ctx == 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, cancel_cb, &inner_sign_ctx, selected_algorithm, &fakebuf, input, algorithms, + num_algorithms); + assert(ret == 0); + ptls_buffer_dispose(&fakebuf); + static uint16_t selected; + selected = *selected_algorithm; + *sign_ctx = &selected; + --server_sc_callcnt; + ++async_sc_callcnt; + return PTLS_ERROR_ASYNC_OPERATION; + } else { + /* second invocation, restore algorithm, and delegate the call */ + assert(algorithms == NULL); + algorithms = *sign_ctx; + num_algorithms = 1; + } + + return sign_certificate(self, tls, cancel_cb, &inner_sign_ctx, 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, void (**cancel_cb)(void *sign_ctx), void **sign_ctx, + 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, cancel_cb, sign_ctx, 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); - if (require_client_authentication) { - ok(sc_callcnt == 2); - } else { - ok(sc_callcnt == 1); - } + 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 == 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 +992,14 @@ static void test_key_update(void) 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 +1050,31 @@ static void test_resumption_impl(int different_preferred_key_share, int require_ 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 +1101,18 @@ static void test_resumption_with_client_authentication(void) 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 +1563,8 @@ static void test_all_handshakes(void) 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);