diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata index 9bc295d4..171b9c70 100644 --- a/crypto/err/ssl.errordata +++ b/crypto/err/ssl.errordata @@ -128,6 +128,7 @@ SSL,271,PSK_IDENTITY_BINDER_COUNT_MISMATCH SSL,195,PSK_IDENTITY_NOT_FOUND SSL,196,PSK_NO_CLIENT_CB SSL,197,PSK_NO_SERVER_CB +SSL,298,QUIC_INTERNAL_ERROR SSL,198,READ_TIMEOUT_EXPIRED SSL,199,RECORD_LENGTH_MISMATCH SSL,200,RECORD_TOO_LARGE @@ -221,6 +222,7 @@ SSL,252,UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY SSL,241,WRONG_CERTIFICATE_TYPE SSL,242,WRONG_CIPHER_RETURNED SSL,243,WRONG_CURVE +SSL,299,WRONG_ENCRYPTION_LEVEL_RECEIVED SSL,244,WRONG_MESSAGE_TYPE SSL,245,WRONG_SIGNATURE_TYPE SSL,246,WRONG_SSL_VERSION diff --git a/include/openssl/base.h b/include/openssl/base.h index f8567e0f..7fe232f3 100644 --- a/include/openssl/base.h +++ b/include/openssl/base.h @@ -394,6 +394,7 @@ typedef struct ssl_cipher_st SSL_CIPHER; typedef struct ssl_ctx_st SSL_CTX; typedef struct ssl_method_st SSL_METHOD; typedef struct ssl_private_key_method_st SSL_PRIVATE_KEY_METHOD; +typedef struct ssl_quic_method_st SSL_QUIC_METHOD; typedef struct ssl_session_st SSL_SESSION; typedef struct ssl_st SSL; typedef struct ssl_ticket_aead_method_st SSL_TICKET_AEAD_METHOD; diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 0f3d1747..5b1c0679 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -3036,6 +3036,113 @@ OPENSSL_EXPORT void SSL_get_peer_quic_transport_params(const SSL *ssl, size_t *out_params_len); +// QUIC integration. +// +// QUIC acts as an underlying transport for the TLS 1.3 handshake. The following +// functions allow a QUIC implementation to serve as the underlying transport as +// described in draft-ietf-quic-tls. +// +// When configured for QUIC, |SSL_do_handshake| will drive the handshake as +// before, but it will not use the configured |BIO|. It will call functions on +// |SSL_QUIC_METHOD| to configure secrets and send data. If data is needed from +// the peer, it will return |SSL_ERROR_WANT_READ|. When received, the caller +// should call |SSL_provide_quic_data| and then |SSL_do_handshake| to continue +// the handshake. It is an error to call |SSL_read| and |SSL_write| in QUIC. +// +// Note that secrets for an encryption level may be available to QUIC before the +// level is active in TLS. Callers should use |SSL_quic_read_level| to determine +// the active read level for |SSL_provide_quic_data|. |SSL_do_handshake| will +// pass the active write level to |SSL_QUIC_METHOD| when writing data. Callers +// can use |SSL_quic_write_level| to query the active write level when +// generating their own errors. +// +// See https://tools.ietf.org/html/draft-ietf-quic-tls-15#section-4.1 for more +// details. +// +// To avoid DoS attacks, the QUIC implementation must limit the amount of data +// being queued up. The implementation can call +// |SSL_quic_max_handshake_flight_len| to get the maximum buffer length at each +// encryption level. +// +// Note: 0-RTT and post-handshake tickets are not currently supported via this +// API. + +// ssl_encryption_level_t represents a specific QUIC encryption level used to +// transmit handshake messages. +enum ssl_encryption_level_t { + ssl_encryption_initial = 0, + ssl_encryption_early_data, + ssl_encryption_handshake, + ssl_encryption_application, +}; + +// ssl_quic_method_st (aka |SSL_QUIC_METHOD|) describes custom QUIC hooks. +struct ssl_quic_method_st { + // set_encryption_secrets configures the read and write secrets for the given + // encryption level. This function will always be called before an encryption + // level other than |ssl_encryption_initial| is used. Note, however, that + // secrets for a level may be configured before TLS is ready to send or accept + // data at that level. + // + // When reading packets at a given level, the QUIC implementation must send + // ACKs at the same level, so this function provides read and write secrets + // together. The exception is |ssl_encryption_early_data|, where secrets are + // only available in the client to server direction. The other secret will be + // NULL. The server acknowledges such data at |ssl_encryption_application|, + // which will be configured in the same |SSL_do_handshake| call. + // + // This function should use |SSL_get_current_cipher| to determine the TLS + // cipher suite. + // + // It returns one on success and zero on error. + int (*set_encryption_secrets)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); + // add_message adds a message to the current flight at the given encryption + // level. A single handshake flight may include multiple encryption levels. + // Callers can defer writing data to the network until |flush_flight| for + // optimal packing. It returns one on success and zero on error. + int (*add_message)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + // flush_flight is called when the current flight is complete and should be + // written to the transport. Note a flight may contain data at several + // encryption levels. It returns one on success and zero on error. + int (*flush_flight)(SSL *ssl); + // send_alert sends a fatal alert at the specified encryption level. It + // returns one on success and zero on error. + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert); +}; + +// SSL_quic_max_handshake_flight_len returns returns the maximum number of bytes +// that may be received at the given encryption level. This function should be +// used to limit buffering in the QUIC implementation. +// +// See https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-4.4. +OPENSSL_EXPORT size_t SSL_quic_max_handshake_flight_len( + const SSL *ssl, enum ssl_encryption_level_t level); + +// SSL_quic_read_level returns the current read encryption level. +OPENSSL_EXPORT enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl); + +// SSL_quic_write_level returns the current write encryption level. +OPENSSL_EXPORT enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl); + +// SSL_provide_quic_data provides data from QUIC at a particular encryption +// level |level|. It is an error to call this function outside of the handshake +// or with an encryption level other than the current read level. It returns one +// on success and zero on error. +OPENSSL_EXPORT int SSL_provide_quic_data(SSL *ssl, + enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + + +// SSL_CTX_set_quic_method configures the QUIC hooks. This should only be +// configured with a minimum version of TLS 1.3. |quic_method| must remain valid +// for the lifetime of |ctx|. It returns one on success and zero on error. +OPENSSL_EXPORT int SSL_CTX_set_quic_method(SSL_CTX *ctx, + const SSL_QUIC_METHOD *quic_method); + + // Early data. // // WARNING: 0-RTT support in BoringSSL is currently experimental and not fully @@ -4795,6 +4902,8 @@ BSSL_NAMESPACE_END #define SSL_R_INVALID_SIGNATURE_ALGORITHM 295 #define SSL_R_DUPLICATE_SIGNATURE_ALGORITHM 296 #define SSL_R_TLS13_DOWNGRADE 297 +#define SSL_R_QUIC_INTERNAL_ERROR 298 +#define SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED 299 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020 diff --git a/ssl/handshake.cc b/ssl/handshake.cc index 963038f5..b1da056e 100644 --- a/ssl/handshake.cc +++ b/ssl/handshake.cc @@ -543,6 +543,16 @@ int ssl_run_handshake(SSL_HANDSHAKE *hs, bool *out_early_return) { case ssl_hs_read_server_hello: case ssl_hs_read_message: case ssl_hs_read_change_cipher_spec: { + if (ssl->ctx->quic_method) { + hs->wait = ssl_hs_ok; + // The change cipher spec is omitted in QUIC. + if (hs->wait != ssl_hs_read_change_cipher_spec) { + ssl->s3->rwstate = SSL_READING; + return -1; + } + break; + } + uint8_t alert = SSL_AD_DECODE_ERROR; size_t consumed = 0; ssl_open_record_t ret; diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc index e46b39f9..24331ba9 100644 --- a/ssl/handshake_client.cc +++ b/ssl/handshake_client.cc @@ -459,8 +459,8 @@ static enum ssl_hs_wait_t do_enter_early_data(SSL_HANDSHAKE *hs) { if (!tls13_init_early_key_schedule(hs, ssl->session->master_key, ssl->session->master_key_length) || !tls13_derive_early_secrets(hs) || - !tls13_set_traffic_key(ssl, evp_aead_seal, hs->early_traffic_secret, - hs->hash_len)) { + !tls13_set_traffic_key(ssl, ssl_encryption_early_data, evp_aead_seal, + hs->early_traffic_secret, hs->hash_len)) { return ssl_hs_error; } diff --git a/ssl/internal.h b/ssl/internal.h index 22189920..a036a17f 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -665,6 +665,12 @@ class SSLAEADContext { Span mac_key, Span fixed_iv); + // CreatePlaceholderForQUIC creates a placeholder |SSLAEADContext| for the + // given cipher and version. The resulting object can be queried for various + // properties but cannot encrypt or decrypt data. + static UniquePtr CreatePlaceholderForQUIC( + uint16_t version, const SSL_CIPHER *cipher); + // SetVersionIfNullCipher sets the version the SSLAEADContext for the null // cipher, to make version-specific determinations in the record layer prior // to a cipher being selected. @@ -1231,7 +1237,8 @@ bool tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in, // tls13_set_traffic_key sets the read or write traffic keys to // |traffic_secret|. It returns true on success and false on error. -bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction, +bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level, + enum evp_aead_direction_t direction, const uint8_t *traffic_secret, size_t traffic_secret_len); @@ -1272,7 +1279,8 @@ bool tls13_finished_mac(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, // tls13_derive_session_psk calculates the PSK for this session based on the // resumption master secret and |nonce|. It returns true on success, and false // on failure. -bool tls13_derive_session_psk(SSL_SESSION *session, Span nonce); +bool tls13_derive_session_psk(SSL_SESSION *session, Span nonce, + bool use_quic); // tls13_write_psk_binder calculates the PSK binder value and replaces the last // bytes of |msg| with the resulting value. It returns true on success, and @@ -2074,6 +2082,9 @@ struct SSL3_STATE { // needs re-doing when in SSL_accept or SSL_connect int rwstate = SSL_NOTHING; + enum ssl_encryption_level_t read_level = ssl_encryption_initial; + enum ssl_encryption_level_t write_level = ssl_encryption_initial; + // early_data_skipped is the amount of early data that has been skipped by the // record layer. uint16_t early_data_skipped = 0; @@ -2790,6 +2801,9 @@ struct ssl_ctx_st { // and is further constrainted by |SSL_OP_NO_*|. uint16_t conf_min_version = 0; + // quic_method is the method table corresponding to the QUIC hooks. + const SSL_QUIC_METHOD *quic_method = nullptr; + // tls13_variant is the variant of TLS 1.3 we are using for this // configuration. tls13_variant_t tls13_variant = tls13_rfc; diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc index 3f09d50b..689dd1d8 100644 --- a/ssl/s3_both.cc +++ b/ssl/s3_both.cc @@ -184,48 +184,56 @@ bool ssl3_finish_message(SSL *ssl, CBB *cbb, Array *out_msg) { } bool ssl3_add_message(SSL *ssl, Array msg) { - // Pack handshake data into the minimal number of records. This avoids - // unnecessary encryption overhead, notably in TLS 1.3 where we send several - // encrypted messages in a row. For now, we do not do this for the null - // cipher. The benefit is smaller and there is a risk of breaking buggy - // implementations. Additionally, we tie this to draft-28 as a sanity check, - // on the off chance middleboxes have fixated on sizes. - // - // TODO(davidben): See if we can do this uniformly. - Span rest = msg; - if (ssl->s3->aead_write_ctx->is_null_cipher() || - ssl->version == TLS1_3_DRAFT23_VERSION) { - while (!rest.empty()) { - Span chunk = rest.subspan(0, ssl->max_send_fragment); - rest = rest.subspan(chunk.size()); - - if (!add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, chunk)) { - return false; - } + if (ssl->ctx->quic_method) { + if (!ssl->ctx->quic_method->add_message(ssl, ssl->s3->write_level, + msg.data(), msg.size())) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; } } else { - while (!rest.empty()) { - // Flush if |pending_hs_data| is full. - if (ssl->s3->pending_hs_data && - ssl->s3->pending_hs_data->length >= ssl->max_send_fragment && - !tls_flush_pending_hs_data(ssl)) { - return false; - } + // Pack handshake data into the minimal number of records. This avoids + // unnecessary encryption overhead, notably in TLS 1.3 where we send several + // encrypted messages in a row. For now, we do not do this for the null + // cipher. The benefit is smaller and there is a risk of breaking buggy + // implementations. Additionally, we tie this to draft-28 as a sanity check, + // on the off chance middleboxes have fixated on sizes. + // + // TODO(davidben): See if we can do this uniformly. + Span rest = msg; + if (ssl->s3->aead_write_ctx->is_null_cipher() || + ssl->version == TLS1_3_DRAFT23_VERSION) { + while (!rest.empty()) { + Span chunk = rest.subspan(0, ssl->max_send_fragment); + rest = rest.subspan(chunk.size()); - size_t pending_len = - ssl->s3->pending_hs_data ? ssl->s3->pending_hs_data->length : 0; - Span chunk = - rest.subspan(0, ssl->max_send_fragment - pending_len); - assert(!chunk.empty()); - rest = rest.subspan(chunk.size()); - - if (!ssl->s3->pending_hs_data) { - ssl->s3->pending_hs_data.reset(BUF_MEM_new()); + if (!add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, chunk)) { + return false; + } } - if (!ssl->s3->pending_hs_data || - !BUF_MEM_append(ssl->s3->pending_hs_data.get(), chunk.data(), - chunk.size())) { - return false; + } else { + while (!rest.empty()) { + // Flush if |pending_hs_data| is full. + if (ssl->s3->pending_hs_data && + ssl->s3->pending_hs_data->length >= ssl->max_send_fragment && + !tls_flush_pending_hs_data(ssl)) { + return false; + } + + size_t pending_len = + ssl->s3->pending_hs_data ? ssl->s3->pending_hs_data->length : 0; + Span chunk = + rest.subspan(0, ssl->max_send_fragment - pending_len); + assert(!chunk.empty()); + rest = rest.subspan(chunk.size()); + + if (!ssl->s3->pending_hs_data) { + ssl->s3->pending_hs_data.reset(BUF_MEM_new()); + } + if (!ssl->s3->pending_hs_data || + !BUF_MEM_append(ssl->s3->pending_hs_data.get(), chunk.data(), + chunk.size())) { + return false; + } } } } @@ -241,7 +249,8 @@ bool ssl3_add_message(SSL *ssl, Array msg) { } bool tls_flush_pending_hs_data(SSL *ssl) { - if (!ssl->s3->pending_hs_data || ssl->s3->pending_hs_data->length == 0) { + if (!ssl->s3->pending_hs_data || ssl->s3->pending_hs_data->length == 0 || + ssl->ctx->quic_method) { return true; } @@ -255,7 +264,11 @@ bool tls_flush_pending_hs_data(SSL *ssl) { bool ssl3_add_change_cipher_spec(SSL *ssl) { static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS}; - if (!tls_flush_pending_hs_data(ssl) || + if (!tls_flush_pending_hs_data(ssl)) { + return false; + } + + if (!ssl->ctx->quic_method && !add_record_to_flight(ssl, SSL3_RT_CHANGE_CIPHER_SPEC, kChangeCipherSpec)) { return false; @@ -267,6 +280,18 @@ bool ssl3_add_change_cipher_spec(SSL *ssl) { } int ssl3_flush_flight(SSL *ssl) { + if (ssl->ctx->quic_method) { + if (ssl->s3->write_shutdown != ssl_shutdown_none) { + OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN); + return -1; + } + + if (!ssl->ctx->quic_method->flush_flight(ssl)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return -1; + } + } + if (!tls_flush_pending_hs_data(ssl)) { return -1; } diff --git a/ssl/s3_pkt.cc b/ssl/s3_pkt.cc index 1ccbf9f1..e9b652e0 100644 --- a/ssl/s3_pkt.cc +++ b/ssl/s3_pkt.cc @@ -163,9 +163,11 @@ int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *in, for (;;) { // max contains the maximum number of bytes that we can put into a record. unsigned max = ssl->max_send_fragment; - if (is_early_data_write && max > ssl->session->ticket_max_early_data - - ssl->s3->hs->early_data_written) { - max = ssl->session->ticket_max_early_data - ssl->s3->hs->early_data_written; + if (is_early_data_write && + max > ssl->session->ticket_max_early_data - + ssl->s3->hs->early_data_written) { + max = + ssl->session->ticket_max_early_data - ssl->s3->hs->early_data_written; if (max == 0) { ssl->s3->wnum = tot; ssl->s3->hs->can_early_write = false; @@ -406,10 +408,19 @@ int ssl_send_alert(SSL *ssl, int level, int desc) { } int ssl3_dispatch_alert(SSL *ssl) { - int ret = do_ssl3_write(ssl, SSL3_RT_ALERT, &ssl->s3->send_alert[0], 2); - if (ret <= 0) { - return ret; + if (ssl->ctx->quic_method) { + if (!ssl->ctx->quic_method->send_alert(ssl, ssl->s3->write_level, + ssl->s3->send_alert[1])) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return 0; + } + } else { + int ret = do_ssl3_write(ssl, SSL3_RT_ALERT, &ssl->s3->send_alert[0], 2); + if (ret <= 0) { + return ret; + } } + ssl->s3->alert_dispatch = 0; // If the alert is fatal, flush the BIO now. diff --git a/ssl/ssl_aead_ctx.cc b/ssl/ssl_aead_ctx.cc index 335f6f48..f01b57dc 100644 --- a/ssl/ssl_aead_ctx.cc +++ b/ssl/ssl_aead_ctx.cc @@ -151,6 +151,11 @@ UniquePtr SSLAEADContext::Create( return aead_ctx; } +UniquePtr SSLAEADContext::CreatePlaceholderForQUIC( + uint16_t version, const SSL_CIPHER *cipher) { + return MakeUnique(version, false, cipher); +} + void SSLAEADContext::SetVersionIfNullCipher(uint16_t version) { if (is_null_cipher()) { version_ = version; diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc index 1f648658..5bd24426 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc @@ -781,6 +781,82 @@ BIO *SSL_get_rbio(const SSL *ssl) { return ssl->rbio.get(); } BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio.get(); } +size_t SSL_quic_max_handshake_flight_len(const SSL *ssl, + enum ssl_encryption_level_t level) { + // Limits flights to 16K by default when there are no large + // (certificate-carrying) messages. + static const size_t kDefaultLimit = 16384; + + switch (level) { + case ssl_encryption_initial: + return kDefaultLimit; + case ssl_encryption_early_data: + // QUIC does not send EndOfEarlyData. + return 0; + case ssl_encryption_handshake: + if (ssl->server) { + // Servers may receive Certificate message if configured to request + // client certificates. + if (!!(ssl->config->verify_mode & SSL_VERIFY_PEER) && + ssl->max_cert_list > kDefaultLimit) { + return ssl->max_cert_list; + } + } else { + // Clients may receive both Certificate message and a CertificateRequest + // message. + if (2*ssl->max_cert_list > kDefaultLimit) { + return 2*ssl->max_cert_list; + } + } + return kDefaultLimit; + case ssl_encryption_application: + // Note there is not actually a bound on the number of NewSessionTickets + // one may send in a row. This level may need more involved flow + // control. See https://github.com/quicwg/base-drafts/issues/1834. + return kDefaultLimit; + } + + return 0; +} + +enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl) { + return ssl->s3->read_level; +} + +enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl) { + return ssl->s3->write_level; +} + +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) { + if (ssl->ctx->quic_method == nullptr) { + OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + + if (level != ssl->s3->read_level) { + OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED); + return 0; + } + + size_t new_len = (ssl->s3->hs_buf ? ssl->s3->hs_buf->length : 0) + len; + if (new_len < len || + new_len > SSL_quic_max_handshake_flight_len(ssl, level)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_EXCESSIVE_MESSAGE_SIZE); + return 0; + } + + // Re-create the handshake buffer if needed. + if (!ssl->s3->hs_buf) { + ssl->s3->hs_buf.reset(BUF_MEM_new()); + if (!ssl->s3->hs_buf) { + return 0; + } + } + + return BUF_MEM_append(ssl->s3->hs_buf.get(), data, len); +} + int SSL_do_handshake(SSL *ssl) { ssl_reset_error_state(ssl); @@ -961,6 +1037,11 @@ int SSL_read(SSL *ssl, void *buf, int num) { } int SSL_peek(SSL *ssl, void *buf, int num) { + if (ssl->ctx->quic_method != nullptr) { + OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + int ret = ssl_read_impl(ssl); if (ret <= 0) { return ret; @@ -977,6 +1058,11 @@ int SSL_peek(SSL *ssl, void *buf, int num) { int SSL_write(SSL *ssl, const void *buf, int num) { ssl_reset_error_state(ssl); + if (ssl->ctx->quic_method != nullptr) { + OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + if (ssl->do_handshake == NULL) { OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED); return -1; @@ -1193,6 +1279,9 @@ int SSL_get_error(const SSL *ssl, int ret_code) { return SSL_ERROR_HANDBACK; case SSL_READING: { + if (ssl->ctx->quic_method) { + return SSL_ERROR_WANT_READ; + } BIO *bio = SSL_get_rbio(ssl); if (BIO_should_read(bio)) { return SSL_ERROR_WANT_READ; @@ -2298,6 +2387,14 @@ char *SSL_get_shared_ciphers(const SSL *ssl, char *buf, int len) { return buf; } +int SSL_CTX_set_quic_method(SSL_CTX *ctx, const SSL_QUIC_METHOD *quic_method) { + if (ctx->method->is_dtls) { + return 0; + } + ctx->quic_method = quic_method; + return 1; +} + int SSL_get_ex_new_index(long argl, void *argp, CRYPTO_EX_unused *unused, CRYPTO_EX_dup *dup_unused, CRYPTO_EX_free *free_func) { int index; diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 61a47d3d..c237809e 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -104,6 +105,26 @@ struct CurveTest { std::vector expected; }; +template +class UnownedSSLExData { + public: + UnownedSSLExData() { + index_ = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + } + + T *Get(const SSL *ssl) { + return index_ < 0 ? nullptr + : static_cast(SSL_get_ex_data(ssl, index_)); + } + + bool Set(SSL *ssl, T *t) { + return index_ >= 0 && SSL_set_ex_data(ssl, index_, t); + } + + private: + int index_; +}; + static const CipherTest kCipherTests[] = { // Selecting individual ciphers should work. { @@ -4422,6 +4443,546 @@ TEST(SSLTest, GetCertificateThreads) { } #endif +constexpr size_t kNumQUICLevels = 4; +static_assert(ssl_encryption_initial < kNumQUICLevels, + "kNumQUICLevels is wrong"); +static_assert(ssl_encryption_early_data < kNumQUICLevels, + "kNumQUICLevels is wrong"); +static_assert(ssl_encryption_handshake < kNumQUICLevels, + "kNumQUICLevels is wrong"); +static_assert(ssl_encryption_application < kNumQUICLevels, + "kNumQUICLevels is wrong"); + +class MockQUICTransport { + public: + MockQUICTransport() { + // The caller is expected to configure initial secrets. + levels_[ssl_encryption_initial].write_secret = {1}; + levels_[ssl_encryption_initial].read_secret = {1}; + } + + void set_peer(MockQUICTransport *peer) { peer_ = peer; } + + bool has_alert() const { return has_alert_; } + ssl_encryption_level_t alert_level() const { return alert_level_; } + uint8_t alert() const { return alert_; } + + bool PeerSecretsMatch(ssl_encryption_level_t level) const { + return levels_[level].write_secret == peer_->levels_[level].read_secret && + levels_[level].read_secret == peer_->levels_[level].write_secret; + } + + bool HasSecrets(ssl_encryption_level_t level) const { + return !levels_[level].write_secret.empty() || + !levels_[level].read_secret.empty(); + } + + bool SetEncryptionSecrets(ssl_encryption_level_t level, + const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len) { + if (HasSecrets(level)) { + ADD_FAILURE() << "duplicate keys configured"; + return false; + } + if (level != ssl_encryption_early_data && + (read_secret == nullptr || write_secret == nullptr)) { + ADD_FAILURE() << "key was unexpectedly null"; + return false; + } + if (read_secret != nullptr) { + levels_[level].read_secret.assign(read_secret, read_secret + secret_len); + } + if (write_secret != nullptr) { + levels_[level].write_secret.assign(write_secret, + write_secret + secret_len); + } + return true; + } + + bool WriteHandshakeData(ssl_encryption_level_t level, + Span data) { + if (levels_[level].write_secret.empty()) { + ADD_FAILURE() << "data written before keys configured"; + return false; + } + levels_[level].write_data.insert(levels_[level].write_data.end(), + data.begin(), data.end()); + return true; + } + + bool SendAlert(ssl_encryption_level_t level, uint8_t alert_value) { + if (has_alert_) { + ADD_FAILURE() << "duplicate alert sent"; + return false; + } + + if (levels_[level].write_secret.empty()) { + ADD_FAILURE() << "alert sent before keys configured"; + return false; + } + + has_alert_ = true; + alert_level_ = level; + alert_ = alert_value; + return true; + } + + bool ReadHandshakeData(std::vector *out, + ssl_encryption_level_t level, + size_t num = std::numeric_limits::max()) { + if (levels_[level].read_secret.empty()) { + ADD_FAILURE() << "data read before keys configured"; + return false; + } + // The peer may not have configured any keys yet. + if (peer_->levels_[level].write_secret.empty()) { + return true; + } + // Check the peer computed the same key. + if (peer_->levels_[level].write_secret != levels_[level].read_secret) { + ADD_FAILURE() << "peer write key does not match read key"; + return false; + } + std::vector *peer_data = &peer_->levels_[level].write_data; + num = std::min(num, peer_data->size()); + out->assign(peer_data->begin(), peer_data->begin() + num); + peer_data->erase(peer_data->begin(), peer_data->begin() + num); + return true; + } + + private: + MockQUICTransport *peer_ = nullptr; + + bool has_alert_ = false; + ssl_encryption_level_t alert_level_ = ssl_encryption_initial; + uint8_t alert_ = 0; + + struct Level { + std::vector write_data; + std::vector write_secret; + std::vector read_secret; + }; + Level levels_[kNumQUICLevels]; +}; + +class MockQUICTransportPair { + public: + MockQUICTransportPair() { + server_.set_peer(&client_); + client_.set_peer(&server_); + } + + ~MockQUICTransportPair() { + server_.set_peer(nullptr); + client_.set_peer(nullptr); + } + + MockQUICTransport *client() { return &client_; } + MockQUICTransport *server() { return &server_; } + + bool SecretsMatch(ssl_encryption_level_t level) const { + return client_.PeerSecretsMatch(level); + } + + private: + MockQUICTransport client_; + MockQUICTransport server_; +}; + +class QUICMethodTest : public testing::Test { + protected: + void SetUp() override { + client_ctx_.reset(SSL_CTX_new(TLS_method())); + server_ctx_.reset(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(client_ctx_); + ASSERT_TRUE(server_ctx_); + + bssl::UniquePtr cert = GetTestCertificate(); + bssl::UniquePtr key = GetTestKey(); + ASSERT_TRUE(cert); + ASSERT_TRUE(key); + ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx_.get(), cert.get())); + ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx_.get(), key.get())); + + SSL_CTX_set_min_proto_version(server_ctx_.get(), TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(server_ctx_.get(), TLS1_3_VERSION); + SSL_CTX_set_min_proto_version(client_ctx_.get(), TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(client_ctx_.get(), TLS1_3_VERSION); + } + + static MockQUICTransport *TransportFromSSL(const SSL *ssl) { + return ex_data_.Get(ssl); + } + + static bool ProvideHandshakeData( + SSL *ssl, size_t num = std::numeric_limits::max()) { + MockQUICTransport *transport = TransportFromSSL(ssl); + ssl_encryption_level_t level = SSL_quic_read_level(ssl); + std::vector data; + return transport->ReadHandshakeData(&data, level, num) && + SSL_provide_quic_data(ssl, level, data.data(), data.size()); + } + + bool CreateClientAndServer() { + client_.reset(SSL_new(client_ctx_.get())); + server_.reset(SSL_new(server_ctx_.get())); + if (!client_ || !server_) { + return false; + } + + SSL_set_connect_state(client_.get()); + SSL_set_accept_state(server_.get()); + + ex_data_.Set(client_.get(), transport_.client()); + ex_data_.Set(server_.get(), transport_.server()); + return true; + } + + // The following functions may be configured on an |SSL_QUIC_METHOD| as + // default implementations. + + static int SetEncryptionSecretsCallback(SSL *ssl, + ssl_encryption_level_t level, + const uint8_t *read_key, + const uint8_t *write_key, + size_t key_len) { + return TransportFromSSL(ssl)->SetEncryptionSecrets(level, read_key, + write_key, key_len); + } + + static int AddMessageCallback(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) { + EXPECT_EQ(level, SSL_quic_write_level(ssl)); + return TransportFromSSL(ssl)->WriteHandshakeData(level, + MakeConstSpan(data, len)); + } + + static int FlushFlightCallback(SSL *ssl) { return 1; } + + static int SendAlertCallback(SSL *ssl, ssl_encryption_level_t level, + uint8_t alert) { + EXPECT_EQ(level, SSL_quic_write_level(ssl)); + return TransportFromSSL(ssl)->SendAlert(level, alert); + } + + bssl::UniquePtr client_ctx_; + bssl::UniquePtr server_ctx_; + + static UnownedSSLExData ex_data_; + MockQUICTransportPair transport_; + + bssl::UniquePtr client_; + bssl::UniquePtr server_; +}; + +UnownedSSLExData QUICMethodTest::ex_data_; + +// Test a full handshake works. +TEST_F(QUICMethodTest, Basic) { + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + AddMessageCallback, + FlushFlightCallback, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + for (;;) { + ASSERT_TRUE(ProvideHandshakeData(client_.get())); + int client_ret = SSL_do_handshake(client_.get()); + if (client_ret != 1) { + ASSERT_EQ(client_ret, -1); + ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ); + } + + ASSERT_TRUE(ProvideHandshakeData(server_.get())); + int server_ret = SSL_do_handshake(server_.get()); + if (server_ret != 1) { + ASSERT_EQ(server_ret, -1); + ASSERT_EQ(SSL_get_error(server_.get(), server_ret), SSL_ERROR_WANT_READ); + } + + if (client_ret == 1 && server_ret == 1) { + break; + } + } + + EXPECT_EQ(SSL_do_handshake(client_.get()), 1); + EXPECT_EQ(SSL_do_handshake(server_.get()), 1); + EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application)); + EXPECT_FALSE(transport_.client()->has_alert()); + EXPECT_FALSE(transport_.server()->has_alert()); + + // The server sent NewSessionTicket messages in the handshake. + // + // TODO(davidben,svaldez): Add an API for the client to consume post-handshake + // messages and update these tests. + std::vector new_session_ticket; + ASSERT_TRUE(transport_.client()->ReadHandshakeData( + &new_session_ticket, ssl_encryption_application)); + EXPECT_FALSE(new_session_ticket.empty()); +} + +// Test only releasing data to QUIC one byte at a time on request, to maximize +// state machine pauses. Additionally, test that existing asynchronous callbacks +// still work. +TEST_F(QUICMethodTest, Async) { + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + AddMessageCallback, + FlushFlightCallback, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + // Install an asynchronous certificate callback. + bool cert_cb_ok = false; + SSL_set_cert_cb(server_.get(), + [](SSL *, void *arg) -> int { + return *static_cast(arg) ? 1 : -1; + }, + &cert_cb_ok); + + for (;;) { + int client_ret = SSL_do_handshake(client_.get()); + if (client_ret != 1) { + ASSERT_EQ(client_ret, -1); + ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ); + ASSERT_TRUE(ProvideHandshakeData(client_.get(), 1)); + } + + int server_ret = SSL_do_handshake(server_.get()); + if (server_ret != 1) { + ASSERT_EQ(server_ret, -1); + int ssl_err = SSL_get_error(server_.get(), server_ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + ASSERT_TRUE(ProvideHandshakeData(server_.get(), 1)); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + ASSERT_FALSE(cert_cb_ok); + cert_cb_ok = true; + break; + default: + FAIL() << "Unexpected SSL_get_error result: " << ssl_err; + } + } + + if (client_ret == 1 && server_ret == 1) { + break; + } + } + + EXPECT_EQ(SSL_do_handshake(client_.get()), 1); + EXPECT_EQ(SSL_do_handshake(server_.get()), 1); + EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application)); + EXPECT_FALSE(transport_.client()->has_alert()); + EXPECT_FALSE(transport_.server()->has_alert()); +} + +// Test buffering write data until explicit flushes. +TEST_F(QUICMethodTest, Buffered) { + struct BufferedFlight { + std::vector data[kNumQUICLevels]; + }; + static UnownedSSLExData buffered_flights; + + auto add_message = [](SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) -> int { + BufferedFlight *flight = buffered_flights.Get(ssl); + flight->data[level].insert(flight->data[level].end(), data, data + len); + return 1; + }; + + auto flush_flight = [](SSL *ssl) -> int { + BufferedFlight *flight = buffered_flights.Get(ssl); + for (size_t level = 0; level < kNumQUICLevels; level++) { + if (!flight->data[level].empty()) { + if (!TransportFromSSL(ssl)->WriteHandshakeData( + static_cast(level), + flight->data[level])) { + return 0; + } + flight->data[level].clear(); + } + } + return 1; + }; + + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + add_message, + flush_flight, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + BufferedFlight client_flight, server_flight; + buffered_flights.Set(client_.get(), &client_flight); + buffered_flights.Set(server_.get(), &server_flight); + + for (;;) { + ASSERT_TRUE(ProvideHandshakeData(client_.get())); + int client_ret = SSL_do_handshake(client_.get()); + if (client_ret != 1) { + ASSERT_EQ(client_ret, -1); + ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ); + } + + ASSERT_TRUE(ProvideHandshakeData(server_.get())); + int server_ret = SSL_do_handshake(server_.get()); + if (server_ret != 1) { + ASSERT_EQ(server_ret, -1); + ASSERT_EQ(SSL_get_error(server_.get(), server_ret), SSL_ERROR_WANT_READ); + } + + if (client_ret == 1 && server_ret == 1) { + break; + } + } + + EXPECT_EQ(SSL_do_handshake(client_.get()), 1); + EXPECT_EQ(SSL_do_handshake(server_.get()), 1); + EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application)); + EXPECT_FALSE(transport_.client()->has_alert()); + EXPECT_FALSE(transport_.server()->has_alert()); +} + +// Test that excess data at one level is rejected. That is, if a single +// |SSL_provide_quic_data| call included both ServerHello and +// EncryptedExtensions in a single chunk, BoringSSL notices and rejects this on +// key change. +TEST_F(QUICMethodTest, ExcessProvidedData) { + auto add_message = [](SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) -> int { + // Switch everything to the initial level. + return TransportFromSSL(ssl)->WriteHandshakeData(ssl_encryption_initial, + MakeConstSpan(data, len)); + }; + + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + add_message, + FlushFlightCallback, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + // Send the ClientHello and ServerHello through Finished. + ASSERT_EQ(SSL_do_handshake(client_.get()), -1); + ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ); + ASSERT_TRUE(ProvideHandshakeData(server_.get())); + ASSERT_EQ(SSL_do_handshake(server_.get()), -1); + ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ); + + // The client is still waiting for the ServerHello at initial + // encryption. + ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get())); + + // |add_message| incorrectly wrote everything at the initial level, so this + // queues up ServerHello through Finished in one chunk. + ASSERT_TRUE(ProvideHandshakeData(client_.get())); + + // The client reads ServerHello successfully, but then rejects the buffered + // EncryptedExtensions on key change. + ASSERT_EQ(SSL_do_handshake(client_.get()), -1); + ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_SSL); + uint32_t err = ERR_get_error(); + EXPECT_EQ(ERR_GET_LIB(err), ERR_LIB_SSL); + EXPECT_EQ(ERR_GET_REASON(err), SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE); + + // The client sends an alert in response to this. + ASSERT_TRUE(transport_.client()->has_alert()); + EXPECT_EQ(transport_.client()->alert_level(), ssl_encryption_initial); + EXPECT_EQ(transport_.client()->alert(), SSL_AD_UNEXPECTED_MESSAGE); + + // Sanity-check client did get far enough to process the ServerHello and + // install keys. + EXPECT_TRUE(transport_.client()->HasSecrets(ssl_encryption_handshake)); +} + +// Test that |SSL_provide_quic_data| will reject data at the wrong level. +TEST_F(QUICMethodTest, ProvideWrongLevel) { + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + AddMessageCallback, + FlushFlightCallback, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + // Send the ClientHello and ServerHello through Finished. + ASSERT_EQ(SSL_do_handshake(client_.get()), -1); + ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ); + ASSERT_TRUE(ProvideHandshakeData(server_.get())); + ASSERT_EQ(SSL_do_handshake(server_.get()), -1); + ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ); + + // The client is still waiting for the ServerHello at initial + // encryption. + ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get())); + + // Data cannot be provided at the next level. + std::vector data; + ASSERT_TRUE( + transport_.client()->ReadHandshakeData(&data, ssl_encryption_initial)); + ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_handshake, + data.data(), data.size())); + ERR_clear_error(); + + // Progress to EncryptedExtensions. + ASSERT_TRUE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial, + data.data(), data.size())); + ASSERT_EQ(SSL_do_handshake(client_.get()), -1); + ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ); + ASSERT_EQ(ssl_encryption_handshake, SSL_quic_read_level(client_.get())); + + // Data cannot be provided at the previous level. + ASSERT_TRUE( + transport_.client()->ReadHandshakeData(&data, ssl_encryption_handshake)); + ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial, + data.data(), data.size())); +} + +TEST_F(QUICMethodTest, TooMuchData) { + const SSL_QUIC_METHOD quic_method = { + SetEncryptionSecretsCallback, + AddMessageCallback, + FlushFlightCallback, + SendAlertCallback, + }; + + ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method)); + ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method)); + ASSERT_TRUE(CreateClientAndServer()); + + size_t limit = + SSL_quic_max_handshake_flight_len(client_.get(), ssl_encryption_initial); + uint8_t b = 0; + for (size_t i = 0; i < limit; i++) { + ASSERT_TRUE( + SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1)); + } + + EXPECT_FALSE( + SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1)); +} + // TODO(davidben): Convert this file to GTest properly. TEST(SSLTest, AllTests) { if (!TestSSL_SESSIONEncoding(kOpenSSLSession) || diff --git a/ssl/ssl_versions.cc b/ssl/ssl_versions.cc index 911fb7e5..7df7fe76 100644 --- a/ssl/ssl_versions.cc +++ b/ssl/ssl_versions.cc @@ -217,6 +217,11 @@ bool ssl_get_version_range(const SSL_HANDSHAKE *hs, uint16_t *out_min_version, uint16_t min_version = hs->config->conf_min_version; uint16_t max_version = hs->config->conf_max_version; + // QUIC requires TLS 1.3. + if (hs->ssl->ctx->quic_method && min_version < TLS1_3_VERSION) { + min_version = TLS1_3_VERSION; + } + // OpenSSL's API for controlling versions entails blacklisting individual // protocols. This has two problems. First, on the client, the protocol can // only express a contiguous range of versions. Second, a library consumer diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc index a02d35d7..299fc14c 100644 --- a/ssl/tls13_both.cc +++ b/ssl/tls13_both.cc @@ -645,7 +645,8 @@ static bool tls13_receive_key_update(SSL *ssl, const SSLMessage &msg) { bool tls13_post_handshake(SSL *ssl, const SSLMessage &msg) { if (msg.type == SSL3_MT_KEY_UPDATE) { ssl->s3->key_update_count++; - if (ssl->s3->key_update_count > kMaxKeyUpdates) { + if (ssl->ctx->quic_method != nullptr || + ssl->s3->key_update_count > kMaxKeyUpdates) { OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_KEY_UPDATES); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE); return false; diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc index 26f5fb99..fb560011 100644 --- a/ssl/tls13_client.cc +++ b/ssl/tls13_client.cc @@ -389,18 +389,17 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) { } if (!tls13_advance_key_schedule(hs, dhe_secret.data(), dhe_secret.size()) || - !ssl_hash_message(hs, msg) || - !tls13_derive_handshake_secrets(hs) || - !tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret, - hs->hash_len)) { + !ssl_hash_message(hs, msg) || !tls13_derive_handshake_secrets(hs) || + !tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_open, + hs->server_handshake_secret, hs->hash_len)) { return ssl_hs_error; } if (!hs->early_data_offered) { // If not sending early data, set client traffic keys now so that alerts are // encrypted. - if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret, - hs->hash_len)) { + if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal, + hs->client_handshake_secret, hs->hash_len)) { return ssl_hs_error; } } @@ -641,8 +640,8 @@ static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) { } if (hs->early_data_offered) { - if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret, - hs->hash_len)) { + if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal, + hs->client_handshake_secret, hs->hash_len)) { return ssl_hs_error; } } @@ -736,10 +735,10 @@ static enum ssl_hs_wait_t do_complete_second_flight(SSL_HANDSHAKE *hs) { } // Derive the final keys and enable them. - if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->server_traffic_secret_0, - hs->hash_len) || - !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_traffic_secret_0, - hs->hash_len) || + if (!tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_open, + hs->server_traffic_secret_0, hs->hash_len) || + !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_seal, + hs->client_traffic_secret_0, hs->hash_len) || !tls13_derive_resumption_secret(hs)) { return ssl_hs_error; } @@ -883,7 +882,8 @@ bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg) { session->timeout = server_timeout; } - if (!tls13_derive_session_psk(session.get(), ticket_nonce)) { + if (!tls13_derive_session_psk(session.get(), ticket_nonce, + ssl->ctx->quic_method != nullptr)) { return false; } diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc index 5e1f19a3..f18084ea 100644 --- a/ssl/tls13_enc.cc +++ b/ssl/tls13_enc.cc @@ -69,19 +69,27 @@ bool tls13_init_early_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *psk, static bool hkdf_expand_label(uint8_t *out, const EVP_MD *digest, const uint8_t *secret, size_t secret_len, const char *label, size_t label_len, - const uint8_t *hash, size_t hash_len, - size_t len) { - static const char kTLS13LabelVersion[] = "tls13 "; + const uint8_t *hash, size_t hash_len, size_t len, + bool use_quic_label) { + static const char kTLS13ProtocolLabel[] = "tls13 "; + static const char kQUICProtocolLabel[] = "quic "; + + const char *protocol_label; + if (use_quic_label) { + protocol_label = kQUICProtocolLabel; + } else { + protocol_label = kTLS13ProtocolLabel; + } ScopedCBB cbb; CBB child; Array hkdf_label; - if (!CBB_init(cbb.get(), 2 + 1 + strlen(kTLS13LabelVersion) + label_len + 1 + - hash_len) || + if (!CBB_init(cbb.get(), + 2 + 1 + strlen(protocol_label) + label_len + 1 + hash_len) || !CBB_add_u16(cbb.get(), len) || !CBB_add_u8_length_prefixed(cbb.get(), &child) || - !CBB_add_bytes(&child, (const uint8_t *)kTLS13LabelVersion, - strlen(kTLS13LabelVersion)) || + !CBB_add_bytes(&child, (const uint8_t *)protocol_label, + strlen(protocol_label)) || !CBB_add_bytes(&child, (const uint8_t *)label, label_len) || !CBB_add_u8_length_prefixed(cbb.get(), &child) || !CBB_add_bytes(&child, hash, hash_len) || @@ -107,7 +115,8 @@ bool tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in, if (!hkdf_expand_label(hs->secret, hs->transcript.Digest(), hs->secret, hs->hash_len, kTLS13LabelDerived, strlen(kTLS13LabelDerived), derive_context, - derive_context_len, hs->hash_len)) { + derive_context_len, hs->hash_len, + hs->ssl->ctx->quic_method != nullptr)) { return false; } @@ -128,10 +137,12 @@ static bool derive_secret(SSL_HANDSHAKE *hs, uint8_t *out, size_t len, return hkdf_expand_label(out, hs->transcript.Digest(), hs->secret, hs->hash_len, label, label_len, context_hash, - context_hash_len, len); + context_hash_len, len, + hs->ssl->ctx->quic_method != nullptr); } -bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction, +bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level, + enum evp_aead_direction_t direction, const uint8_t *traffic_secret, size_t traffic_secret_len) { const SSL_SESSION *session = SSL_get_session(ssl); @@ -142,36 +153,48 @@ bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction, return false; } - // Look up cipher suite properties. - const EVP_AEAD *aead; - size_t discard; - if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher, - version, SSL_is_dtls(ssl))) { - return false; + UniquePtr traffic_aead; + if (ssl->ctx->quic_method == nullptr) { + // Look up cipher suite properties. + const EVP_AEAD *aead; + size_t discard; + if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher, + version, SSL_is_dtls(ssl))) { + return false; + } + + const EVP_MD *digest = ssl_session_get_digest(session); + + // Derive the key. + size_t key_len = EVP_AEAD_key_length(aead); + uint8_t key[EVP_AEAD_MAX_KEY_LENGTH]; + if (!hkdf_expand_label(key, digest, traffic_secret, traffic_secret_len, + "key", 3, NULL, 0, key_len, + ssl->ctx->quic_method != nullptr)) { + return false; + } + + // Derive the IV. + size_t iv_len = EVP_AEAD_nonce_length(aead); + uint8_t iv[EVP_AEAD_MAX_NONCE_LENGTH]; + if (!hkdf_expand_label(iv, digest, traffic_secret, traffic_secret_len, "iv", + 2, NULL, 0, iv_len, + ssl->ctx->quic_method != nullptr)) { + return false; + } + + + traffic_aead = SSLAEADContext::Create( + direction, session->ssl_version, SSL_is_dtls(ssl), session->cipher, + MakeConstSpan(key, key_len), Span(), + MakeConstSpan(iv, iv_len)); + } else { + // Install a placeholder SSLAEADContext so that SSL accessors work. The + // encryption itself will be handled by the SSL_QUIC_METHOD. + traffic_aead = + SSLAEADContext::CreatePlaceholderForQUIC(version, session->cipher); } - const EVP_MD *digest = ssl_session_get_digest(session); - - // Derive the key. - size_t key_len = EVP_AEAD_key_length(aead); - uint8_t key[EVP_AEAD_MAX_KEY_LENGTH]; - if (!hkdf_expand_label(key, digest, traffic_secret, traffic_secret_len, "key", - 3, NULL, 0, key_len)) { - return false; - } - - // Derive the IV. - size_t iv_len = EVP_AEAD_nonce_length(aead); - uint8_t iv[EVP_AEAD_MAX_NONCE_LENGTH]; - if (!hkdf_expand_label(iv, digest, traffic_secret, traffic_secret_len, "iv", - 2, NULL, 0, iv_len)) { - return false; - } - - UniquePtr traffic_aead = - SSLAEADContext::Create(direction, session->ssl_version, SSL_is_dtls(ssl), - session->cipher, MakeConstSpan(key, key_len), - Span(), MakeConstSpan(iv, iv_len)); if (!traffic_aead) { return false; } @@ -191,10 +214,12 @@ bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction, OPENSSL_memmove(ssl->s3->read_traffic_secret, traffic_secret, traffic_secret_len); ssl->s3->read_traffic_secret_len = traffic_secret_len; + ssl->s3->read_level = level; } else { OPENSSL_memmove(ssl->s3->write_traffic_secret, traffic_secret, traffic_secret_len); ssl->s3->write_traffic_secret_len = traffic_secret_len; + ssl->s3->write_level = level; } return true; @@ -223,40 +248,103 @@ bool tls13_derive_early_secrets(SSL_HANDSHAKE *hs) { return false; } ssl->s3->early_exporter_secret_len = hs->hash_len; + + if (ssl->ctx->quic_method != nullptr) { + if (ssl->server) { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_early_data, nullptr, hs->early_traffic_secret, + hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } else { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_early_data, hs->early_traffic_secret, nullptr, + hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } + } + return true; } bool tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; - return derive_secret(hs, hs->client_handshake_secret, hs->hash_len, - kTLS13LabelClientHandshakeTraffic, - strlen(kTLS13LabelClientHandshakeTraffic)) && - ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET", - hs->client_handshake_secret, hs->hash_len) && - derive_secret(hs, hs->server_handshake_secret, hs->hash_len, - kTLS13LabelServerHandshakeTraffic, - strlen(kTLS13LabelServerHandshakeTraffic)) && - ssl_log_secret(ssl, "SERVER_HANDSHAKE_TRAFFIC_SECRET", - hs->server_handshake_secret, hs->hash_len); + if (!derive_secret(hs, hs->client_handshake_secret, hs->hash_len, + kTLS13LabelClientHandshakeTraffic, + strlen(kTLS13LabelClientHandshakeTraffic)) || + !ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + hs->client_handshake_secret, hs->hash_len) || + !derive_secret(hs, hs->server_handshake_secret, hs->hash_len, + kTLS13LabelServerHandshakeTraffic, + strlen(kTLS13LabelServerHandshakeTraffic)) || + !ssl_log_secret(ssl, "SERVER_HANDSHAKE_TRAFFIC_SECRET", + hs->server_handshake_secret, hs->hash_len)) { + return false; + } + + if (ssl->ctx->quic_method != nullptr) { + if (ssl->server) { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_handshake, hs->client_handshake_secret, + hs->server_handshake_secret, hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } else { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_handshake, hs->server_handshake_secret, + hs->client_handshake_secret, hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } + } + + return true; } bool tls13_derive_application_secrets(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; ssl->s3->exporter_secret_len = hs->hash_len; - return derive_secret(hs, hs->client_traffic_secret_0, hs->hash_len, - kTLS13LabelClientApplicationTraffic, - strlen(kTLS13LabelClientApplicationTraffic)) && - ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0", - hs->client_traffic_secret_0, hs->hash_len) && - derive_secret(hs, hs->server_traffic_secret_0, hs->hash_len, - kTLS13LabelServerApplicationTraffic, - strlen(kTLS13LabelServerApplicationTraffic)) && - ssl_log_secret(ssl, "SERVER_TRAFFIC_SECRET_0", - hs->server_traffic_secret_0, hs->hash_len) && - derive_secret(hs, ssl->s3->exporter_secret, hs->hash_len, - kTLS13LabelExporter, strlen(kTLS13LabelExporter)) && - ssl_log_secret(ssl, "EXPORTER_SECRET", ssl->s3->exporter_secret, - hs->hash_len); + if (!derive_secret(hs, hs->client_traffic_secret_0, hs->hash_len, + kTLS13LabelClientApplicationTraffic, + strlen(kTLS13LabelClientApplicationTraffic)) || + !ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0", + hs->client_traffic_secret_0, hs->hash_len) || + !derive_secret(hs, hs->server_traffic_secret_0, hs->hash_len, + kTLS13LabelServerApplicationTraffic, + strlen(kTLS13LabelServerApplicationTraffic)) || + !ssl_log_secret(ssl, "SERVER_TRAFFIC_SECRET_0", + hs->server_traffic_secret_0, hs->hash_len) || + !derive_secret(hs, ssl->s3->exporter_secret, hs->hash_len, + kTLS13LabelExporter, strlen(kTLS13LabelExporter)) || + !ssl_log_secret(ssl, "EXPORTER_SECRET", ssl->s3->exporter_secret, + hs->hash_len)) { + return false; + } + + if (ssl->ctx->quic_method != nullptr) { + if (ssl->server) { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_application, hs->client_traffic_secret_0, + hs->server_traffic_secret_0, hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } else { + if (!ssl->ctx->quic_method->set_encryption_secrets( + ssl, ssl_encryption_application, hs->server_traffic_secret_0, + hs->client_traffic_secret_0, hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR); + return false; + } + } + } + + return true; } static const char kTLS13LabelApplicationTraffic[] = "traffic upd"; @@ -273,13 +361,15 @@ bool tls13_rotate_traffic_key(SSL *ssl, enum evp_aead_direction_t direction) { } const EVP_MD *digest = ssl_session_get_digest(SSL_get_session(ssl)); - if (!hkdf_expand_label( - secret, digest, secret, secret_len, kTLS13LabelApplicationTraffic, - strlen(kTLS13LabelApplicationTraffic), NULL, 0, secret_len)) { + if (!hkdf_expand_label(secret, digest, secret, secret_len, + kTLS13LabelApplicationTraffic, + strlen(kTLS13LabelApplicationTraffic), NULL, 0, + secret_len, ssl->ctx->quic_method != nullptr)) { return false; } - return tls13_set_traffic_key(ssl, direction, secret, secret_len); + return tls13_set_traffic_key(ssl, ssl_encryption_application, direction, + secret, secret_len); } static const char kTLS13LabelResumption[] = "res master"; @@ -302,11 +392,13 @@ static const char kTLS13LabelFinished[] = "finished"; static bool tls13_verify_data(const EVP_MD *digest, uint16_t version, uint8_t *out, size_t *out_len, const uint8_t *secret, size_t hash_len, - uint8_t *context, size_t context_len) { + uint8_t *context, size_t context_len, + bool use_quic) { uint8_t key[EVP_MAX_MD_SIZE]; unsigned len; if (!hkdf_expand_label(key, digest, secret, hash_len, kTLS13LabelFinished, - strlen(kTLS13LabelFinished), NULL, 0, hash_len) || + strlen(kTLS13LabelFinished), NULL, 0, hash_len, + use_quic) || HMAC(digest, key, hash_len, context, context_len, out, &len) == NULL) { return false; } @@ -328,7 +420,8 @@ bool tls13_finished_mac(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, if (!hs->transcript.GetHash(context_hash, &context_hash_len) || !tls13_verify_data(hs->transcript.Digest(), hs->ssl->version, out, out_len, traffic_secret, hs->hash_len, context_hash, - context_hash_len)) { + context_hash_len, + hs->ssl->ctx->quic_method != nullptr)) { return 0; } return 1; @@ -336,12 +429,13 @@ bool tls13_finished_mac(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, static const char kTLS13LabelResumptionPSK[] = "resumption"; -bool tls13_derive_session_psk(SSL_SESSION *session, Span nonce) { +bool tls13_derive_session_psk(SSL_SESSION *session, Span nonce, + bool use_quic) { const EVP_MD *digest = ssl_session_get_digest(session); return hkdf_expand_label(session->master_key, digest, session->master_key, session->master_key_length, kTLS13LabelResumptionPSK, strlen(kTLS13LabelResumptionPSK), nonce.data(), - nonce.size(), session->master_key_length); + nonce.size(), session->master_key_length, use_quic); } static const char kTLS13LabelExportKeying[] = "exporter"; @@ -370,11 +464,12 @@ bool tls13_export_keying_material(SSL *ssl, Span out, nullptr) && hkdf_expand_label(derived_secret, digest, secret.data(), secret.size(), label.data(), label.size(), export_context, - export_context_len, derived_secret_len) && + export_context_len, derived_secret_len, + ssl->ctx->quic_method != nullptr) && hkdf_expand_label(out.data(), digest, derived_secret, derived_secret_len, kTLS13LabelExportKeying, strlen(kTLS13LabelExportKeying), hash, hash_len, - out.size()); + out.size(), ssl->ctx->quic_method != nullptr); } static const char kTLS13LabelPSKBinder[] = "res binder"; @@ -382,7 +477,7 @@ static const char kTLS13LabelPSKBinder[] = "res binder"; static bool tls13_psk_binder(uint8_t *out, uint16_t version, const EVP_MD *digest, uint8_t *psk, size_t psk_len, uint8_t *context, size_t context_len, - size_t hash_len) { + size_t hash_len, bool use_quic) { uint8_t binder_context[EVP_MAX_MD_SIZE]; unsigned binder_context_len; if (!EVP_Digest(NULL, 0, binder_context, &binder_context_len, digest, NULL)) { @@ -400,9 +495,10 @@ static bool tls13_psk_binder(uint8_t *out, uint16_t version, size_t len; if (!hkdf_expand_label(binder_key, digest, early_secret, hash_len, kTLS13LabelPSKBinder, strlen(kTLS13LabelPSKBinder), - binder_context, binder_context_len, hash_len) || + binder_context, binder_context_len, hash_len, + use_quic) || !tls13_verify_data(digest, version, out, &len, binder_key, hash_len, - context, context_len)) { + context, context_len, use_quic)) { return false; } @@ -435,7 +531,7 @@ bool tls13_write_psk_binder(SSL_HANDSHAKE *hs, uint8_t *msg, size_t len) { if (!tls13_psk_binder(verify_data, ssl->session->ssl_version, digest, ssl->session->master_key, ssl->session->master_key_length, context, context_len, - hash_len)) { + hash_len, ssl->ctx->quic_method != nullptr)) { return false; } @@ -467,16 +563,16 @@ bool tls13_verify_psk_binder(SSL_HANDSHAKE *hs, SSL_SESSION *session, CBS binder; if (!tls13_psk_binder(verify_data, hs->ssl->version, hs->transcript.Digest(), session->master_key, session->master_key_length, - context, context_len, hash_len) || + context, context_len, hash_len, + hs->ssl->ctx->quic_method != nullptr) || // We only consider the first PSK, so compare against the first binder. !CBS_get_u8_length_prefixed(binders, &binder)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); return false; } - bool binder_ok = - CBS_len(&binder) == hash_len && - CRYPTO_memcmp(CBS_data(&binder), verify_data, hash_len) == 0; + bool binder_ok = CBS_len(&binder) == hash_len && + CRYPTO_memcmp(CBS_data(&binder), verify_data, hash_len) == 0; #if defined(BORINGSSL_UNSAFE_FUZZER_MODE) binder_ok = true; #endif diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc index 0d82d68b..969d4b13 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc @@ -195,7 +195,8 @@ static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) { !CBB_add_u8_length_prefixed(&body, &nonce_cbb) || !CBB_add_bytes(&nonce_cbb, nonce, sizeof(nonce)) || !CBB_add_u16_length_prefixed(&body, &ticket) || - !tls13_derive_session_psk(session.get(), nonce) || + !tls13_derive_session_psk(session.get(), nonce, + ssl->ctx->quic_method != nullptr) || !ssl_encrypt_ticket(hs, &ticket, session.get()) || !CBB_add_u16_length_prefixed(&body, &extensions)) { return false; @@ -586,8 +587,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) { // Derive and enable the handshake traffic secrets. if (!tls13_derive_handshake_secrets(hs) || - !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_handshake_secret, - hs->hash_len)) { + !tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal, + hs->server_handshake_secret, hs->hash_len)) { return ssl_hs_error; } @@ -697,8 +698,8 @@ static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) { // Update the secret to the master secret and derive traffic keys. !tls13_advance_key_schedule(hs, kZeroes, hs->hash_len) || !tls13_derive_application_secrets(hs) || - !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_traffic_secret_0, - hs->hash_len)) { + !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_seal, + hs->server_traffic_secret_0, hs->hash_len)) { return ssl_hs_error; } @@ -750,8 +751,8 @@ static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) { static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; if (ssl->s3->early_data_accepted) { - if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->early_traffic_secret, - hs->hash_len)) { + if (!tls13_set_traffic_key(ssl, ssl_encryption_early_data, evp_aead_open, + hs->early_traffic_secret, hs->hash_len)) { return ssl_hs_error; } hs->can_early_write = true; @@ -785,8 +786,8 @@ static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) { ssl->method->next_message(ssl); } } - if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret, - hs->hash_len)) { + if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_open, + hs->client_handshake_secret, hs->hash_len)) { return ssl_hs_error; } hs->tls13_state = ssl->s3->early_data_accepted @@ -892,8 +893,8 @@ static enum ssl_hs_wait_t do_read_client_finished(SSL_HANDSHAKE *hs) { // and derived the resumption secret. !tls13_process_finished(hs, msg, ssl->s3->early_data_accepted) || // evp_aead_seal keys have already been switched. - !tls13_set_traffic_key(ssl, evp_aead_open, hs->client_traffic_secret_0, - hs->hash_len)) { + !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_open, + hs->client_traffic_secret_0, hs->hash_len)) { return ssl_hs_error; }