Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async public key operations #291

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions include/picotls.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ extern "C" {
#define PTLS_ERROR_COMPRESSION_FAILURE (PTLS_ERROR_CLASS_INTERNAL + 8)
#define PTLS_ERROR_ESNI_RETRY (PTLS_ERROR_CLASS_INTERNAL + 8)
#define PTLS_ERROR_REJECT_EARLY_DATA (PTLS_ERROR_CLASS_INTERNAL + 9)
#define PTLS_ERROR_ASYNC_OPERATION (PTLS_ERROR_CLASS_INTERNAL + 10)

#define PTLS_ERROR_INCORRECT_BASE64 (PTLS_ERROR_CLASS_INTERNAL + 50)
#define PTLS_ERROR_PEM_LABEL_NOT_FOUND (PTLS_ERROR_CLASS_INTERNAL + 51)
Expand Down Expand Up @@ -511,10 +512,15 @@ 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);
/**
* 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`.
*/
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 **sign_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
Expand Down
4 changes: 2 additions & 2 deletions lib/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -920,8 +920,8 @@ 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 int sign_certificate(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_openssl_sign_certificate_t *self = (ptls_openssl_sign_certificate_t *)_self;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second time runs in sign_certificate, it will fail to find the scheme_md

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional.

By the time sign_certificate callback is called for the second time, the signature using an algorithm selected by the async crypto would already be available. The callback no longer needs access to the list of algorithms that the client has offered.

I understand it causes a bit of pain when implementing a fake sign_certificate callback, but my preference goes to keeping the TLS stack simple. FWIW, the fake callback added in 5dcab74 caches the selected algorithm.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I saw it, and it works perfectly.

const struct st_ptls_openssl_signature_scheme_t *scheme;
Expand Down
136 changes: 96 additions & 40 deletions lib/picotls.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,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 */
Expand Down Expand Up @@ -248,6 +249,8 @@ 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;
void *sign_certificate_ctx;
} server;
};
/**
Expand Down Expand Up @@ -375,6 +378,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_complete_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)
{
Expand Down Expand Up @@ -2547,9 +2552,9 @@ 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)
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)
{
static ptls_emit_certificate_t default_emit_certificate = {default_emit_certificate_cb};
ptls_emit_certificate_t *emit_certificate =
Expand All @@ -2565,26 +2570,43 @@ static int send_certificate_and_certificate_verify(ptls_t *tls, ptls_message_emi
if ((ret = emit_certificate->cb(emit_certificate, tls, emitter, tls->key_schedule, context, push_status_request)) != 0)
goto Exit;

/* 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;
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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second run here the start_off will be 0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be either.

When calling ptls_handshake, it is up to the application if it provides a fresh output buffer, or a buffer that already has some data.

int ret;

if (tls->ctx->sign_certificate == NULL)
return 0;

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will be called twice, and we may save the info for efficiency

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed!

I think there is no need to generate signdata when this send_certificate_verify is called the second time. I will update the code so that the sixth argument to the sign_certificate callback would be ptls_iovec_init(NULL, 0) when send_certificate_verify is called the second time.

if ((ret = tls->ctx->sign_certificate->cb(tls->ctx->sign_certificate, tls, &tls->server.sign_certificate_ctx, &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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if returning to the start, then it requires to push message again. Could we optimize it by keeping this offset?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot keep the offset, as the application is allowed to modify the buffer once ptls_handshake returns, or to pass a different buffer which might be empty when calling ptls_handshake the second time. Therefore, the only feasible option would be to cache the bytes that we have built for the CertificateVerify message and reuse it.

That said, I do not think it's worth the effort. There are very few amount of work done before reaching this line. The cost of redoing them is going to be much much less than encrypting the TLS record that would contain the CertificateVerify message.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, understand.

}
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;
Expand Down Expand Up @@ -2843,9 +2865,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);
if ((ret = send_certificate(tls, emitter, &tls->client.certificate_request.signature_algorithms,
tls->client.certificate_request.context, 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)
Expand Down Expand Up @@ -3805,6 +3828,7 @@ 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) {
Expand Down Expand Up @@ -3916,23 +3940,54 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
});
});
});

if (ret != 0) {
if (ret != 0)
goto Exit;
}
}
/* send certificate */
if ((ret = send_certificate(tls, emitter, &ch.signature_algorithms, ptls_iovec_init(NULL, 0), ch.status_request)) != 0)
goto Exit;
/* send certificateverify, finished, and complete the handshake */
if ((ret = server_complete_handshake(tls, emitter, 1, &ch.signature_algorithms)) != 0)
goto Exit;
} else {
/* send finished, and complete the handshake */
if ((ret = server_complete_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);
}
return ret;

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);
#undef EMIT_SERVER_HELLO
#undef EMIT_HELLO_RETRY_REQUEST
}

int server_complete_handshake(ptls_t *tls, ptls_message_emitter_t *emitter, int send_cert_verify,
struct st_ptls_signature_algorithms_t *signature_algorithms)
{
int ret;

if (ret != 0) {
/* send certificateverify if requested */
if (send_cert_verify) {
if ((ret = send_certificate_verify(tls, emitter, signature_algorithms, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING)) !=
0) {
/* signature generation might be an async operation, in that case */
if (ret == PTLS_ERROR_ASYNC_OPERATION)
tls->state = PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY;
goto Exit;
}
}

/* send finished */
if ((ret = send_finished(tls, emitter)) != 0)
goto Exit;

/* update traffic secret, keys */
assert(tls->key_schedule->generation == 2);
if ((ret = key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0))) != 0)
goto Exit;
Expand All @@ -3958,7 +4013,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;
}
Expand All @@ -3970,15 +4025,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);
}
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)
Expand Down Expand Up @@ -4648,6 +4695,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_complete_handshake(tls, &emitter.super, 1, NULL);
default:
break;
}
Expand All @@ -4673,6 +4722,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 */
Expand Down Expand Up @@ -5226,7 +5276,13 @@ 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};

assert(input);
if (tls->state == PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY) {
int ret;
if ((ret = server_complete_handshake(tls, &emitter.super, 1, NULL)) != 0)
return ret;
}

assert(input != NULL);

if (ptls_get_read_epoch(tls) != in_epoch)
return PTLS_ALERT_UNEXPECTED_MESSAGE;
Expand Down
4 changes: 2 additions & 2 deletions lib/uecc.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ 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,
ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
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;
uint8_t hash[32], sig[64];
Expand Down
2 changes: 1 addition & 1 deletion t/minicrypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading