Skip to content
This repository was archived by the owner on Apr 10, 2025. It is now read-only.
Merged
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,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 @@ -3705,6 +3705,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;
Copy link
Member

Choose a reason for hiding this comment

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

Just to have it written down somewhere discoverable, why do we go into SSL_EARLY_DATA_WRITE_RETRY here rather than SSL_EARLY_DATA_FINISHED_WRITING?

Copy link
Author

Choose a reason for hiding this comment

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

It looks like SSL_EARLY_DATA_FINISHED_WRITING works fine here.
I think SSL_EARLY_DATA_FINISHED_WRITING is a better choice.

s->rwstate = SSL_READING;
ret = 0;
}
}
#endif
return ret;
}

Expand Down
79 changes: 63 additions & 16 deletions ssl/ssl_quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,24 +258,49 @@ 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,
SSL_F_QUIC_SET_ENCRYPTION_SECRETS,
ERR_R_INTERNAL_ERROR);
return 0;
}
c = SSL_SESSION_get0_cipher(ssl->psksession);
}

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

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

if (c != NULL)
md = SSL_CIPHER_get_handshake_digest(c);
/*
* 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 @@ -334,3 +359,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) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we know that ssl->server will always be accurate by the time this function is called?
Its value has some rather unfortunate historical behavior -- at SSL_new() time, it is set to 1 if the SSL_METHOD of the parent SSL_CTX has a ssl_accept function set. This is true of, for example, the output of TLS_method() (but not TLS_client_method()). It is forced to zero by SSL_set_connect_state(), whether called directly by the application or as a side effect of SSL_connect().

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 @@ -965,6 +965,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
96 changes: 79 additions & 17 deletions ssl/tls13_enc.c
Original file line number Diff line number Diff line change
Expand Up @@ -468,21 +468,81 @@ 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_F_QUIC_CHANGE_CIPHER_STATE,
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, SSL_F_QUIC_CHANGE_CIPHER_STATE,
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.
*/
Copy link
Member

Choose a reason for hiding this comment

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

I would not have minded if this comment was duplicated in quic_set_encryption_secrets(), but it's not actually needed and it also gave me a chance to independently re-derive the reasoning :)

if (!ossl_assert(s->psksession != NULL
&& s->max_early_data
== s->psksession->ext.max_early_data)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR,
SSL_F_QUIC_CHANGE_CIPHER_STATE, ERR_R_INTERNAL_ERROR);
goto err;
}
sslcipher = SSL_SESSION_get0_cipher(s->psksession);
}
if (sslcipher == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_QUIC_CHANGE_CIPHER_STATE,
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, SSL_F_QUIC_CHANGE_CIPHER_STATE,
ERR_R_MALLOC_FAILURE);
goto err;
}
md = ssl_md(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, SSL_F_QUIC_CHANGE_CIPHER_STATE,
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, SSL_F_QUIC_CHANGE_CIPHER_STATE,
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 @@ -588,10 +648,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