Skip to content

Commit

Permalink
QUIC: Add early data support (#11)
Browse files Browse the repository at this point in the history
* QUIC: Add early data support

This commit adds SSL_set_quic_early_data_enabled to add early data
support to QUIC.
  • Loading branch information
tatsuhiro-t authored and tmshort committed Jul 5, 2022
1 parent b9198c3 commit 8671468
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 33 deletions.
10 changes: 9 additions & 1 deletion doc/man3/SSL_CTX_set_quic_method.pod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ SSL_is_quic,
SSL_get_peer_quic_transport_version,
SSL_get_quic_transport_version,
SSL_set_quic_transport_version,
SSL_set_quic_use_legacy_codepoint
SSL_set_quic_use_legacy_codepoint,
SSL_set_quic_early_data_enabled
- QUIC support

=head1 SYNOPSIS
Expand Down Expand Up @@ -47,6 +48,7 @@ SSL_set_quic_use_legacy_codepoint
void SSL_set_quic_transport_version(SSL *ssl, int version);
int SSL_get_quic_transport_version(const SSL *ssl);
int SSL_get_peer_quic_transport_version(const SSL *ssl);
void SSL_set_quic_early_data_enabled(SSL *ssl, int enabled);

=head1 DESCRIPTION

Expand Down Expand Up @@ -106,6 +108,12 @@ SSL_set_quic_transport_version().
SSL_get_peer_quic_transport_version() returns the version the that was
negotiated.

SSL_set_quic_early_data_enabled() enables QUIC early data if a nonzero
value is passed. Client must set a resumed session before calling
this function. Server must set 0xffffffffu to
SSL_CTX_set_max_early_data() or SSL_set_max_early_data() so that a
session ticket indicates that server is able to accept early data.

=head1 NOTES

These APIs are implementations of BoringSSL's QUIC APIs.
Expand Down
2 changes: 2 additions & 0 deletions include/openssl/ssl.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -2577,6 +2577,8 @@ __owur int SSL_get_peer_quic_transport_version(const SSL *ssl);

int SSL_CIPHER_get_prf_nid(const SSL_CIPHER *c);

void SSL_set_quic_early_data_enabled(SSL *ssl, int enabled);

# endif

# ifdef __cplusplus
Expand Down
15 changes: 15 additions & 0 deletions ssl/ssl_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -3964,6 +3964,21 @@ int SSL_do_handshake(SSL *s)
ret = s->handshake_func(s);
}
}
#ifndef OPENSSL_NO_QUIC
if (SSL_IS_QUIC(s) && ret == 1) {
if (s->server) {
if (s->early_data_state == SSL_EARLY_DATA_ACCEPTING) {
s->early_data_state = SSL_EARLY_DATA_FINISHED_READING;
s->rwstate = SSL_READING;
ret = 0;
}
} else if (s->early_data_state == SSL_EARLY_DATA_CONNECTING) {
s->early_data_state = SSL_EARLY_DATA_WRITE_RETRY;
s->rwstate = SSL_READING;
ret = 0;
}
}
#endif
return ret;
}

Expand Down
76 changes: 60 additions & 16 deletions ssl/ssl_quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,24 +257,46 @@ int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL level)
return 1;
}

md = ssl_handshake_md(ssl);
if (md == NULL) {
/* May not have selected cipher, yet */
const SSL_CIPHER *c = NULL;

/*
* It probably doesn't make sense to use an (external) PSK session,
* but in theory some kinds of external session caches could be
* implemented using it, so allow psksession to be used as well as
* the regular session.
*/
if (ssl->session != NULL)
c = SSL_SESSION_get0_cipher(ssl->session);
else if (ssl->psksession != NULL)
if (level == ssl_encryption_early_data) {
const SSL_CIPHER *c = SSL_SESSION_get0_cipher(ssl->session);
if (ssl->early_data_state == SSL_EARLY_DATA_CONNECTING
&& ssl->max_early_data > 0
&& ssl->session->ext.max_early_data == 0) {
if (!ossl_assert(ssl->psksession != NULL
&& ssl->max_early_data ==
ssl->psksession->ext.max_early_data)) {
SSLfatal(ssl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
c = SSL_SESSION_get0_cipher(ssl->psksession);
}

if (c == NULL) {
SSLfatal(ssl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}

if (c != NULL)
md = SSL_CIPHER_get_handshake_digest(c);
md = ssl_md(ssl->ctx, c->algorithm2);
} else {
md = ssl_handshake_md(ssl);
if (md == NULL) {
/* May not have selected cipher, yet */
const SSL_CIPHER *c = NULL;

/*
* It probably doesn't make sense to use an (external) PSK session,
* but in theory some kinds of external session caches could be
* implemented using it, so allow psksession to be used as well as
* the regular session.
*/
if (ssl->session != NULL)
c = SSL_SESSION_get0_cipher(ssl->session);
else if (ssl->psksession != NULL)
c = SSL_SESSION_get0_cipher(ssl->psksession);

if (c != NULL)
md = SSL_CIPHER_get_handshake_digest(c);
}
}

if ((len = EVP_MD_size(md)) <= 0) {
Expand Down Expand Up @@ -330,3 +352,25 @@ int SSL_is_quic(SSL* ssl)
{
return SSL_IS_QUIC(ssl);
}

void SSL_set_quic_early_data_enabled(SSL *ssl, int enabled)
{
if (!SSL_is_quic(ssl) || !SSL_in_before(ssl))
return;

if (!enabled) {
ssl->early_data_state = SSL_EARLY_DATA_NONE;
return;
}

if (ssl->server) {
ssl->early_data_state = SSL_EARLY_DATA_ACCEPTING;
return;
}

if ((ssl->session == NULL || ssl->session->ext.max_early_data == 0)
&& ssl->psk_use_session_cb == NULL)
return;

ssl->early_data_state = SSL_EARLY_DATA_CONNECTING;
}
10 changes: 10 additions & 0 deletions ssl/statem/statem_srvr.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,16 @@ WORK_STATE ossl_statem_server_post_work(SSL *s, WORK_STATE wst)
SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_SERVER_WRITE))
/* SSLfatal() already called */
return WORK_ERROR;

#ifndef OPENSSL_NO_QUIC
if (SSL_IS_QUIC(s) && s->ext.early_data == SSL_EARLY_DATA_ACCEPTED) {
s->early_data_state = SSL_EARLY_DATA_FINISHED_READING;
if (!s->method->ssl3_enc->change_cipher_state(
s, SSL3_CC_HANDSHAKE | SSL3_CHANGE_CIPHER_SERVER_READ))
/* SSLfatal() already called */
return WORK_ERROR;
}
#endif
}
break;

Expand Down
90 changes: 74 additions & 16 deletions ssl/tls13_enc.c
Original file line number Diff line number Diff line change
Expand Up @@ -430,20 +430,76 @@ static int quic_change_cipher_state(SSL *s, int which)
int is_server_write = ((which & SSL3_CHANGE_CIPHER_SERVER_WRITE) == SSL3_CHANGE_CIPHER_SERVER_WRITE);
int is_early = (which & SSL3_CC_EARLY);

md = ssl_handshake_md(s);
if (!ssl3_digest_cached_records(s, 1)
|| !ssl_handshake_hash(s, hash, sizeof(hash), &hashlen)) {
/* SSLfatal() already called */;
goto err;
}
if (is_early) {
EVP_MD_CTX *mdctx = NULL;
long handlen;
void *hdata;
unsigned int hashlenui;
const SSL_CIPHER *sslcipher = SSL_SESSION_get0_cipher(s->session);

handlen = BIO_get_mem_data(s->s3.handshake_buffer, &hdata);
if (handlen <= 0) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_HANDSHAKE_LENGTH);
goto err;
}

/* Ensure cast to size_t is safe */
hashleni = EVP_MD_size(md);
if (!ossl_assert(hashleni >= 0)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_EVP_LIB);
goto err;
if (s->early_data_state == SSL_EARLY_DATA_CONNECTING
&& s->max_early_data > 0
&& s->session->ext.max_early_data == 0) {
/*
* If we are attempting to send early data, and we've decided to
* actually do it but max_early_data in s->session is 0 then we
* must be using an external PSK.
*/
if (!ossl_assert(s->psksession != NULL
&& s->max_early_data ==
s->psksession->ext.max_early_data)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
sslcipher = SSL_SESSION_get0_cipher(s->psksession);
}
if (sslcipher == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_PSK);
goto err;
}

/*
* We need to calculate the handshake digest using the digest from
* the session. We haven't yet selected our ciphersuite so we can't
* use ssl_handshake_md().
*/
mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_MALLOC_FAILURE);
goto err;
}
md = ssl_md(s->ctx, sslcipher->algorithm2);
if (md == NULL || !EVP_DigestInit_ex(mdctx, md, NULL)
|| !EVP_DigestUpdate(mdctx, hdata, handlen)
|| !EVP_DigestFinal_ex(mdctx, hash, &hashlenui)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
EVP_MD_CTX_free(mdctx);
goto err;
}
hashlen = hashlenui;
EVP_MD_CTX_free(mdctx);
} else {
md = ssl_handshake_md(s);
if (!ssl3_digest_cached_records(s, 1)
|| !ssl_handshake_hash(s, hash, sizeof(hash), &hashlen)) {
/* SSLfatal() already called */;
goto err;
}

/* Ensure cast to size_t is safe */
hashleni = EVP_MD_size(md);
if (!ossl_assert(hashleni >= 0)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_EVP_LIB);
goto err;
}
hashlen = (size_t)hashleni;
}
hashlen = (size_t)hashleni;

if (is_client_read || is_server_write) {
if (is_handshake) {
Expand Down Expand Up @@ -549,10 +605,12 @@ static int quic_change_cipher_state(SSL *s, int which)
}
}

if (s->server)
s->quic_read_level = level;
else
s->quic_write_level = level;
if (level != ssl_encryption_early_data) {
if (s->server)
s->quic_read_level = level;
else
s->quic_write_level = level;
}
}

ret = 1;
Expand Down
Loading

0 comments on commit 8671468

Please sign in to comment.