| // Copyright (c) 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quic/core/tls_handshaker.h" |
| |
| #include "absl/base/macros.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "third_party/boringssl/src/include/openssl/crypto.h" |
| #include "third_party/boringssl/src/include/openssl/ssl.h" |
| #include "quic/core/quic_crypto_stream.h" |
| #include "quic/core/tls_client_handshaker.h" |
| #include "quic/platform/api/quic_bug_tracker.h" |
| #include "quic/platform/api/quic_stack_trace.h" |
| |
| namespace quic { |
| |
| #define ENDPOINT (SSL_is_server(ssl()) ? "TlsServer: " : "TlsClient: ") |
| |
| TlsHandshaker::ProofVerifierCallbackImpl::ProofVerifierCallbackImpl( |
| TlsHandshaker* parent) |
| : parent_(parent) {} |
| |
| TlsHandshaker::ProofVerifierCallbackImpl::~ProofVerifierCallbackImpl() {} |
| |
| void TlsHandshaker::ProofVerifierCallbackImpl::Run( |
| bool ok, |
| const std::string& /*error_details*/, |
| std::unique_ptr<ProofVerifyDetails>* details) { |
| if (parent_ == nullptr) { |
| return; |
| } |
| |
| parent_->verify_details_ = std::move(*details); |
| parent_->verify_result_ = ok ? ssl_verify_ok : ssl_verify_invalid; |
| parent_->set_expected_ssl_error(SSL_ERROR_WANT_READ); |
| parent_->proof_verify_callback_ = nullptr; |
| if (parent_->verify_details_) { |
| parent_->OnProofVerifyDetailsAvailable(*parent_->verify_details_); |
| } |
| parent_->AdvanceHandshake(); |
| } |
| |
| void TlsHandshaker::ProofVerifierCallbackImpl::Cancel() { |
| parent_ = nullptr; |
| } |
| |
| TlsHandshaker::TlsHandshaker(QuicCryptoStream* stream, QuicSession* session) |
| : stream_(stream), handshaker_delegate_(session) {} |
| |
| TlsHandshaker::~TlsHandshaker() { |
| if (proof_verify_callback_) { |
| proof_verify_callback_->Cancel(); |
| } |
| } |
| |
| bool TlsHandshaker::ProcessInput(absl::string_view input, |
| EncryptionLevel level) { |
| if (parser_error_ != QUIC_NO_ERROR) { |
| return false; |
| } |
| // TODO(nharper): Call SSL_quic_read_level(ssl()) and check whether the |
| // encryption level BoringSSL expects matches the encryption level that we |
| // just received input at. If they mismatch, should ProcessInput return true |
| // or false? If data is for a future encryption level, it should be queued for |
| // later? |
| if (SSL_provide_quic_data(ssl(), TlsConnection::BoringEncryptionLevel(level), |
| reinterpret_cast<const uint8_t*>(input.data()), |
| input.size()) != 1) { |
| // SSL_provide_quic_data can fail for 3 reasons: |
| // - API misuse (calling it before SSL_set_custom_quic_method, which we |
| // call in the TlsHandshaker c'tor) |
| // - Memory exhaustion when appending data to its buffer |
| // - Data provided at the wrong encryption level |
| // |
| // Of these, the only sensible error to handle is data provided at the wrong |
| // encryption level. |
| // |
| // Note: the error provided below has a good-sounding enum value, although |
| // it doesn't match the description as it's a QUIC Crypto specific error. |
| parser_error_ = QUIC_INVALID_CRYPTO_MESSAGE_TYPE; |
| parser_error_detail_ = "TLS stack failed to receive data"; |
| return false; |
| } |
| AdvanceHandshake(); |
| return true; |
| } |
| |
| void TlsHandshaker::AdvanceHandshake() { |
| if (is_connection_closed_) { |
| return; |
| } |
| if (GetHandshakeState() >= HANDSHAKE_COMPLETE) { |
| ProcessPostHandshakeMessage(); |
| return; |
| } |
| |
| QUICHE_BUG_IF( |
| quic_tls_server_async_done_no_flusher, |
| SSL_is_server(ssl()) && !handshaker_delegate_->PacketFlusherAttached()) |
| << "is_server:" << SSL_is_server(ssl()); |
| |
| QUIC_VLOG(1) << ENDPOINT << "Continuing handshake"; |
| int rv = SSL_do_handshake(ssl()); |
| |
| // If SSL_do_handshake return success(1) and we are in early data, it is |
| // possible that we have provided ServerHello to BoringSSL but it hasn't been |
| // processed. Retry SSL_do_handshake once will advance the handshake more in |
| // that case. If there are no unprocessed ServerHello, the retry will return a |
| // non-positive number. |
| if (rv == 1 && SSL_in_early_data(ssl())) { |
| OnEnterEarlyData(); |
| rv = SSL_do_handshake(ssl()); |
| QUIC_VLOG(1) << ENDPOINT |
| << "SSL_do_handshake returned when entering early data. After " |
| << "retry, rv=" << rv |
| << ", SSL_in_early_data=" << SSL_in_early_data(ssl()); |
| // The retry should either |
| // - Return <= 0 if the handshake is still pending, likely still in early |
| // data. |
| // - Return 1 if the handshake has _actually_ finished. i.e. |
| // SSL_in_early_data should be false. |
| // |
| // In either case, it should not both return 1 and stay in early data. |
| if (rv == 1 && SSL_in_early_data(ssl()) && !is_connection_closed_) { |
| QUIC_BUG(quic_handshaker_stay_in_early_data) |
| << "The original and the retry of SSL_do_handshake both returned " |
| "success and in early data"; |
| CloseConnection(QUIC_HANDSHAKE_FAILED, |
| "TLS handshake failed: Still in early data after retry"); |
| return; |
| } |
| } |
| |
| if (rv == 1) { |
| FinishHandshake(); |
| return; |
| } |
| int ssl_error = SSL_get_error(ssl(), rv); |
| if (ssl_error == expected_ssl_error_) { |
| return; |
| } |
| if (ShouldCloseConnectionOnUnexpectedError(ssl_error) && |
| !is_connection_closed_) { |
| QUIC_VLOG(1) << "SSL_do_handshake failed; SSL_get_error returns " |
| << ssl_error; |
| ERR_print_errors_fp(stderr); |
| CloseConnection(QUIC_HANDSHAKE_FAILED, "TLS handshake failed"); |
| } |
| } |
| |
| void TlsHandshaker::CloseConnection(QuicErrorCode error, |
| const std::string& reason_phrase) { |
| QUICHE_DCHECK(!reason_phrase.empty()); |
| stream()->OnUnrecoverableError(error, reason_phrase); |
| is_connection_closed_ = true; |
| } |
| |
| void TlsHandshaker::CloseConnection(QuicErrorCode error, |
| QuicIetfTransportErrorCodes ietf_error, |
| const std::string& reason_phrase) { |
| QUICHE_DCHECK(!reason_phrase.empty()); |
| stream()->OnUnrecoverableError(error, ietf_error, reason_phrase); |
| is_connection_closed_ = true; |
| } |
| |
| void TlsHandshaker::OnConnectionClosed(QuicErrorCode /*error*/, |
| ConnectionCloseSource /*source*/) { |
| is_connection_closed_ = true; |
| } |
| |
| bool TlsHandshaker::ShouldCloseConnectionOnUnexpectedError(int /*ssl_error*/) { |
| return true; |
| } |
| |
| size_t TlsHandshaker::BufferSizeLimitForLevel(EncryptionLevel level) const { |
| return SSL_quic_max_handshake_flight_len( |
| ssl(), TlsConnection::BoringEncryptionLevel(level)); |
| } |
| |
| ssl_early_data_reason_t TlsHandshaker::EarlyDataReason() const { |
| return SSL_get_early_data_reason(ssl()); |
| } |
| |
| const EVP_MD* TlsHandshaker::Prf(const SSL_CIPHER* cipher) { |
| return EVP_get_digestbynid(SSL_CIPHER_get_prf_nid(cipher)); |
| } |
| |
| enum ssl_verify_result_t TlsHandshaker::VerifyCert(uint8_t* out_alert) { |
| if (verify_result_ != ssl_verify_retry || |
| expected_ssl_error() == SSL_ERROR_WANT_CERTIFICATE_VERIFY) { |
| enum ssl_verify_result_t result = verify_result_; |
| verify_result_ = ssl_verify_retry; |
| *out_alert = cert_verify_tls_alert_; |
| return result; |
| } |
| const STACK_OF(CRYPTO_BUFFER)* cert_chain = SSL_get0_peer_certificates(ssl()); |
| if (cert_chain == nullptr) { |
| *out_alert = SSL_AD_INTERNAL_ERROR; |
| return ssl_verify_invalid; |
| } |
| // TODO(nharper): Pass the CRYPTO_BUFFERs into the QUIC stack to avoid copies. |
| std::vector<std::string> certs; |
| for (CRYPTO_BUFFER* cert : cert_chain) { |
| certs.push_back( |
| std::string(reinterpret_cast<const char*>(CRYPTO_BUFFER_data(cert)), |
| CRYPTO_BUFFER_len(cert))); |
| } |
| |
| ProofVerifierCallbackImpl* proof_verify_callback = |
| new ProofVerifierCallbackImpl(this); |
| |
| cert_verify_tls_alert_ = *out_alert; |
| QuicAsyncStatus verify_result = VerifyCertChain( |
| certs, &cert_verify_error_details_, &verify_details_, |
| &cert_verify_tls_alert_, |
| std::unique_ptr<ProofVerifierCallback>(proof_verify_callback)); |
| switch (verify_result) { |
| case QUIC_SUCCESS: |
| if (verify_details_) { |
| OnProofVerifyDetailsAvailable(*verify_details_); |
| } |
| return ssl_verify_ok; |
| case QUIC_PENDING: |
| proof_verify_callback_ = proof_verify_callback; |
| set_expected_ssl_error(SSL_ERROR_WANT_CERTIFICATE_VERIFY); |
| return ssl_verify_retry; |
| case QUIC_FAILURE: |
| default: |
| *out_alert = cert_verify_tls_alert_; |
| QUIC_LOG(INFO) << "Cert chain verification failed: " |
| << cert_verify_error_details_; |
| return ssl_verify_invalid; |
| } |
| } |
| |
| void TlsHandshaker::SetWriteSecret(EncryptionLevel level, |
| const SSL_CIPHER* cipher, |
| const std::vector<uint8_t>& write_secret) { |
| QUIC_DVLOG(1) << ENDPOINT << "SetWriteSecret level=" << level; |
| std::unique_ptr<QuicEncrypter> encrypter = |
| QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); |
| const EVP_MD* prf = Prf(cipher); |
| CryptoUtils::SetKeyAndIV(prf, write_secret, encrypter.get()); |
| std::vector<uint8_t> header_protection_key = |
| CryptoUtils::GenerateHeaderProtectionKey(prf, write_secret, |
| encrypter->GetKeySize()); |
| encrypter->SetHeaderProtectionKey( |
| absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), |
| header_protection_key.size())); |
| if (level == ENCRYPTION_FORWARD_SECURE) { |
| QUICHE_DCHECK(latest_write_secret_.empty()); |
| latest_write_secret_ = write_secret; |
| one_rtt_write_header_protection_key_ = header_protection_key; |
| } |
| handshaker_delegate_->OnNewEncryptionKeyAvailable(level, |
| std::move(encrypter)); |
| } |
| |
| bool TlsHandshaker::SetReadSecret(EncryptionLevel level, |
| const SSL_CIPHER* cipher, |
| const std::vector<uint8_t>& read_secret) { |
| QUIC_DVLOG(1) << ENDPOINT << "SetReadSecret level=" << level; |
| std::unique_ptr<QuicDecrypter> decrypter = |
| QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); |
| const EVP_MD* prf = Prf(cipher); |
| CryptoUtils::SetKeyAndIV(prf, read_secret, decrypter.get()); |
| std::vector<uint8_t> header_protection_key = |
| CryptoUtils::GenerateHeaderProtectionKey(prf, read_secret, |
| decrypter->GetKeySize()); |
| decrypter->SetHeaderProtectionKey( |
| absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), |
| header_protection_key.size())); |
| if (level == ENCRYPTION_FORWARD_SECURE) { |
| QUICHE_DCHECK(latest_read_secret_.empty()); |
| latest_read_secret_ = read_secret; |
| one_rtt_read_header_protection_key_ = header_protection_key; |
| } |
| return handshaker_delegate_->OnNewDecryptionKeyAvailable( |
| level, std::move(decrypter), |
| /*set_alternative_decrypter=*/false, |
| /*latch_once_used=*/false); |
| } |
| |
| std::unique_ptr<QuicDecrypter> |
| TlsHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter() { |
| if (latest_read_secret_.empty() || latest_write_secret_.empty() || |
| one_rtt_read_header_protection_key_.empty() || |
| one_rtt_write_header_protection_key_.empty()) { |
| std::string error_details = "1-RTT secret(s) not set yet."; |
| QUIC_BUG(quic_bug_10312_1) << error_details; |
| CloseConnection(QUIC_INTERNAL_ERROR, error_details); |
| return nullptr; |
| } |
| const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl()); |
| const EVP_MD* prf = Prf(cipher); |
| latest_read_secret_ = |
| CryptoUtils::GenerateNextKeyPhaseSecret(prf, latest_read_secret_); |
| latest_write_secret_ = |
| CryptoUtils::GenerateNextKeyPhaseSecret(prf, latest_write_secret_); |
| |
| std::unique_ptr<QuicDecrypter> decrypter = |
| QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); |
| CryptoUtils::SetKeyAndIV(prf, latest_read_secret_, decrypter.get()); |
| decrypter->SetHeaderProtectionKey(absl::string_view( |
| reinterpret_cast<char*>(one_rtt_read_header_protection_key_.data()), |
| one_rtt_read_header_protection_key_.size())); |
| |
| return decrypter; |
| } |
| |
| std::unique_ptr<QuicEncrypter> TlsHandshaker::CreateCurrentOneRttEncrypter() { |
| if (latest_write_secret_.empty() || |
| one_rtt_write_header_protection_key_.empty()) { |
| std::string error_details = "1-RTT write secret not set yet."; |
| QUIC_BUG(quic_bug_10312_2) << error_details; |
| CloseConnection(QUIC_INTERNAL_ERROR, error_details); |
| return nullptr; |
| } |
| const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl()); |
| std::unique_ptr<QuicEncrypter> encrypter = |
| QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); |
| CryptoUtils::SetKeyAndIV(Prf(cipher), latest_write_secret_, encrypter.get()); |
| encrypter->SetHeaderProtectionKey(absl::string_view( |
| reinterpret_cast<char*>(one_rtt_write_header_protection_key_.data()), |
| one_rtt_write_header_protection_key_.size())); |
| return encrypter; |
| } |
| |
| bool TlsHandshaker::ExportKeyingMaterialForLabel(absl::string_view label, |
| absl::string_view context, |
| size_t result_len, |
| std::string* result) { |
| // TODO(haoyuewang) Adding support of keying material export when 0-RTT is |
| // accepted. |
| if (SSL_in_init(ssl())) { |
| return false; |
| } |
| result->resize(result_len); |
| return SSL_export_keying_material( |
| ssl(), reinterpret_cast<uint8_t*>(&*result->begin()), result_len, |
| label.data(), label.size(), |
| reinterpret_cast<const uint8_t*>(context.data()), context.size(), |
| !context.empty()) == 1; |
| } |
| |
| void TlsHandshaker::WriteMessage(EncryptionLevel level, |
| absl::string_view data) { |
| stream_->WriteCryptoData(level, data); |
| } |
| |
| void TlsHandshaker::FlushFlight() {} |
| |
| void TlsHandshaker::SendAlert(EncryptionLevel level, uint8_t desc) { |
| std::string error_details = absl::StrCat( |
| "TLS handshake failure (", EncryptionLevelToString(level), ") ", |
| static_cast<int>(desc), ": ", SSL_alert_desc_string_long(desc)); |
| QUIC_DLOG(ERROR) << error_details; |
| CloseConnection( |
| TlsAlertToQuicErrorCode(desc), |
| static_cast<QuicIetfTransportErrorCodes>(CRYPTO_ERROR_FIRST + desc), |
| error_details); |
| } |
| |
| } // namespace quic |