Use TicketCrypter to enable TLS session resumption in QUIC. gfe-relnote: Adds support for session resumption in TLS-based versions of QUIC. Protected by quic_enable_tls_resumption. PiperOrigin-RevId: 308357681 Change-Id: I3889a8eec65d3903967d6ab1ca7c1b997da79606
diff --git a/quic/core/crypto/quic_crypto_server_config.cc b/quic/core/crypto/quic_crypto_server_config.cc index 4dc34ff..36372ac 100644 --- a/quic/core/crypto/quic_crypto_server_config.cc +++ b/quic/core/crypto/quic_crypto_server_config.cc
@@ -242,7 +242,7 @@ proof_source_(std::move(proof_source)), client_cert_mode_(ClientCertMode::kNone), key_exchange_source_(std::move(key_exchange_source)), - ssl_ctx_(TlsServerConnection::CreateSslCtx()), + ssl_ctx_(TlsServerConnection::CreateSslCtx(proof_source_.get())), source_address_token_future_secs_(3600), source_address_token_lifetime_secs_(86400), enable_serving_sct_(false),
diff --git a/quic/core/crypto/tls_server_connection.cc b/quic/core/crypto/tls_server_connection.cc index bdc941a..b647ecc 100644 --- a/quic/core/crypto/tls_server_connection.cc +++ b/quic/core/crypto/tls_server_connection.cc
@@ -4,6 +4,8 @@ #include "net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h" +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" namespace quic { @@ -13,12 +15,21 @@ delegate_(delegate) {} // static -bssl::UniquePtr<SSL_CTX> TlsServerConnection::CreateSslCtx() { +bssl::UniquePtr<SSL_CTX> TlsServerConnection::CreateSslCtx( + ProofSource* proof_source) { bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx(); SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), &SelectCertificateCallback); SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), &SelectAlpnCallback, nullptr); - SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_NO_TICKET); + // We don't actually need the SessionTicketCrypter here, but we need to know + // whether it's set. + if (GetQuicReloadableFlag(quic_enable_tls_resumption) && + proof_source->SessionTicketCrypter()) { + SSL_CTX_set_ticket_aead_method(ssl_ctx.get(), + &TlsServerConnection::kSessionTicketMethod); + } else { + SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_NO_TICKET); + } return ssl_ctx; } @@ -81,4 +92,41 @@ max_out); } +// static +const SSL_TICKET_AEAD_METHOD TlsServerConnection::kSessionTicketMethod{ + TlsServerConnection::SessionTicketMaxOverhead, + TlsServerConnection::SessionTicketSeal, + TlsServerConnection::SessionTicketOpen, +}; + +// static +size_t TlsServerConnection::SessionTicketMaxOverhead(SSL* ssl) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketMaxOverhead(); +} + +// static +int TlsServerConnection::SessionTicketSeal(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketSeal( + out, out_len, max_out_len, + quiche::QuicheStringPiece(reinterpret_cast<const char*>(in), in_len)); +} + +// static +enum ssl_ticket_aead_result_t TlsServerConnection::SessionTicketOpen( + SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len) { + return ConnectionFromSsl(ssl)->delegate_->SessionTicketOpen( + out, out_len, max_out_len, + quiche::QuicheStringPiece(reinterpret_cast<const char*>(in), in_len)); +} + } // namespace quic
diff --git a/quic/core/crypto/tls_server_connection.h b/quic/core/crypto/tls_server_connection.h index 85ce7e7..6da8114 100644 --- a/quic/core/crypto/tls_server_connection.h +++ b/quic/core/crypto/tls_server_connection.h
@@ -5,6 +5,7 @@ #ifndef QUICHE_QUIC_CORE_CRYPTO_TLS_SERVER_CONNECTION_H_ #define QUICHE_QUIC_CORE_CRYPTO_TLS_SERVER_CONNECTION_H_ +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" #include "net/third_party/quiche/src/quic/core/crypto/tls_connection.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" @@ -59,6 +60,50 @@ size_t* out_len, size_t max_out) = 0; + // The following functions are used to implement an SSL_TICKET_AEAD_METHOD. + // See + // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_ticket_aead_result_t + // for details on the BoringSSL API. + + // SessionTicketMaxOverhead returns the maximum number of bytes of overhead + // that SessionTicketSeal may add when encrypting a session ticket. + virtual size_t SessionTicketMaxOverhead() = 0; + + // SessionTicketSeal encrypts the session ticket in |in|, putting the + // resulting encrypted ticket in |out|, writing the length of the bytes + // written to |*out_len|, which is no larger than |max_out_len|. It returns + // 1 on success and 0 on error. + virtual int SessionTicketSeal(uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) = 0; + + // SessionTicketOpen is called when BoringSSL has an encrypted session + // ticket |in| and wants the ticket decrypted. This decryption operation can + // happen synchronously or asynchronously. + // + // If the decrypted ticket is not available at the time of the function + // call, this function returns ssl_ticket_aead_retry. If this function + // returns ssl_ticket_aead_retry, then SSL_do_handshake will return + // SSL_ERROR_PENDING_TICKET. Once the pending ticket decryption has + // completed, SSL_do_handshake needs to be called again. + // + // When this function is called and the decrypted ticket is available + // (either the ticket was decrypted synchronously, or an asynchronous + // operation has completed and SSL_do_handshake has been called again), the + // decrypted ticket is put in |out|, and the length of that output is + // written to |*out_len|, not to exceed |max_out_len|, and + // ssl_ticket_aead_success is returned. If the ticket cannot be decrypted + // and should be ignored, this function returns + // ssl_ticket_aead_ignore_ticket and a full handshake will be performed + // instead. If a fatal error occurs, ssl_ticket_aead_error can be returned + // which will terminate the handshake. + virtual enum ssl_ticket_aead_result_t SessionTicketOpen( + uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) = 0; + // Provides the delegate for callbacks that are shared between client and // server. virtual TlsConnection::Delegate* ConnectionDelegate() = 0; @@ -69,7 +114,7 @@ TlsServerConnection(SSL_CTX* ssl_ctx, Delegate* delegate); // Creates and configures an SSL_CTX that is appropriate for servers to use. - static bssl::UniquePtr<SSL_CTX> CreateSslCtx(); + static bssl::UniquePtr<SSL_CTX> CreateSslCtx(ProofSource* proof_source); void SetCertChain(const std::vector<CRYPTO_BUFFER*>& cert_chain); @@ -105,6 +150,25 @@ size_t* out_len, size_t max_out); + // Implementation of SSL_TICKET_AEAD_METHOD which delegates to corresponding + // methods in TlsServerConnection::Delegate (a.k.a. TlsServerHandshaker). + static const SSL_TICKET_AEAD_METHOD kSessionTicketMethod; + + // The following functions make up the contents of |kSessionTicketMethod|. + static size_t SessionTicketMaxOverhead(SSL* ssl); + static int SessionTicketSeal(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len); + static enum ssl_ticket_aead_result_t SessionTicketOpen(SSL* ssl, + uint8_t* out, + size_t* out_len, + size_t max_out_len, + const uint8_t* in, + size_t in_len); + Delegate* delegate_; };
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc index 75ff75d..108bc5a 100644 --- a/quic/core/tls_server_handshaker.cc +++ b/quic/core/tls_server_handshaker.cc
@@ -44,6 +44,42 @@ handshaker_ = nullptr; } +TlsServerHandshaker::DecryptCallback::DecryptCallback( + TlsServerHandshaker* handshaker) + : handshaker_(handshaker) {} + +void TlsServerHandshaker::DecryptCallback::Run(std::vector<uint8_t> plaintext) { + if (handshaker_ == nullptr) { + // The callback was cancelled before we could run. + return; + } + handshaker_->decrypted_session_ticket_ = std::move(plaintext); + // DecryptCallback::Run could be called synchronously. When that happens, we + // are currently in the middle of a call to AdvanceHandshake. + // (AdvanceHandshake called SSL_do_handshake, which through some layers called + // SessionTicketOpen, which called TicketCrypter::Decrypt, which synchronously + // called this function.) In that case, the handshake will continue to be + // processed when this function returns. + // + // When this callback is called asynchronously (i.e. the ticket decryption is + // pending), TlsServerHandshaker is not actively processing handshake + // messages. We need to have it resume processing handshake messages by + // calling AdvanceHandshake. + if (handshaker_->state_ == STATE_TICKET_DECRYPTION_PENDING) { + handshaker_->AdvanceHandshake(); + } + // The TicketDecrypter took ownership of this callback when Decrypt was + // called. Once the callback returns, it will be deleted. Remove the + // (non-owning) pointer to the callback from the handshaker so the handshaker + // doesn't have an invalid pointer hanging around. + handshaker_->ticket_decryption_callback_ = nullptr; +} + +void TlsServerHandshaker::DecryptCallback::Cancel() { + DCHECK(handshaker_); + handshaker_ = nullptr; +} + TlsServerHandshaker::TlsServerHandshaker( QuicSession* session, const QuicCryptoServerConfig& crypto_config) @@ -69,6 +105,10 @@ signature_callback_->Cancel(); signature_callback_ = nullptr; } + if (ticket_decryption_callback_) { + ticket_decryption_callback_->Cancel(); + ticket_decryption_callback_ = nullptr; + } } bool TlsServerHandshaker::GetBase64SHA256ClientChannelID( @@ -196,6 +236,9 @@ case STATE_SIGNATURE_PENDING: should_close = ssl_error != SSL_ERROR_WANT_PRIVATE_KEY_OPERATION; break; + case STATE_TICKET_DECRYPTION_PENDING: + should_close = ssl_error != SSL_ERROR_PENDING_TICKET; + break; default: should_close = true; } @@ -373,6 +416,71 @@ return ssl_private_key_success; } +size_t TlsServerHandshaker::SessionTicketMaxOverhead() { + DCHECK(proof_source_->SessionTicketCrypter()); + return proof_source_->SessionTicketCrypter()->MaxOverhead(); +} + +int TlsServerHandshaker::SessionTicketSeal(uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) { + DCHECK(proof_source_->SessionTicketCrypter()); + std::vector<uint8_t> ticket = + proof_source_->SessionTicketCrypter()->Encrypt(in); + if (max_out_len < ticket.size()) { + QUIC_BUG + << "SessionTicketCrypter returned " << ticket.size() + << " bytes of ciphertext, which is larger than its max overhead of " + << max_out_len; + return 0; // failure + } + *out_len = ticket.size(); + memcpy(out, ticket.data(), ticket.size()); + return 1; // success +} + +ssl_ticket_aead_result_t TlsServerHandshaker::SessionTicketOpen( + uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) { + DCHECK(proof_source_->SessionTicketCrypter()); + + if (!ticket_decryption_callback_) { + ticket_decryption_callback_ = new DecryptCallback(this); + proof_source_->SessionTicketCrypter()->Decrypt( + in, std::unique_ptr<DecryptCallback>(ticket_decryption_callback_)); + // Decrypt can run the callback synchronously. In that case, the callback + // will clear the ticket_decryption_callback_ pointer, and instead of + // returning ssl_ticket_aead_retry, we should continue processing to return + // the decrypted ticket. + // + // If the callback is not run asynchronously, return ssl_ticket_aead_retry + // and when the callback is complete this function will be run again to + // return the result. + if (ticket_decryption_callback_) { + state_ = STATE_TICKET_DECRYPTION_PENDING; + return ssl_ticket_aead_retry; + } + } + ticket_decryption_callback_ = nullptr; + state_ = STATE_LISTENING; + if (decrypted_session_ticket_.empty()) { + QUIC_DLOG(ERROR) << "Session ticket decryption failed; ignoring ticket"; + // Ticket decryption failed. Ignore the ticket. + return ssl_ticket_aead_ignore_ticket; + } + if (max_out_len < decrypted_session_ticket_.size()) { + return ssl_ticket_aead_error; + } + memcpy(out, decrypted_session_ticket_.data(), + decrypted_session_ticket_.size()); + *out_len = decrypted_session_ticket_.size(); + + return ssl_ticket_aead_success; +} + int TlsServerHandshaker::SelectCertificate(int* out_alert) { const char* hostname = SSL_get_servername(ssl(), TLSEXT_NAMETYPE_host_name); if (hostname) {
diff --git a/quic/core/tls_server_handshaker.h b/quic/core/tls_server_handshaker.h index 066c53b..9d72d30 100644 --- a/quic/core/tls_server_handshaker.h +++ b/quic/core/tls_server_handshaker.h
@@ -106,6 +106,16 @@ ssl_private_key_result_t PrivateKeyComplete(uint8_t* out, size_t* out_len, size_t max_out) override; + size_t SessionTicketMaxOverhead() override; + int SessionTicketSeal(uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) override; + ssl_ticket_aead_result_t SessionTicketOpen( + uint8_t* out, + size_t* out_len, + size_t max_out_len, + quiche::QuicheStringPiece in) override; TlsConnection::Delegate* ConnectionDelegate() override { return this; } private: @@ -124,8 +134,22 @@ TlsServerHandshaker* handshaker_; }; + class QUIC_EXPORT_PRIVATE DecryptCallback + : public ProofSource::DecryptCallback { + public: + explicit DecryptCallback(TlsServerHandshaker* handshaker); + void Run(std::vector<uint8_t> plaintext) override; + + // If called, Cancel causes the pending callback to be a no-op. + void Cancel(); + + private: + TlsServerHandshaker* handshaker_; + }; + enum State { STATE_LISTENING, + STATE_TICKET_DECRYPTION_PENDING, STATE_SIGNATURE_PENDING, STATE_SIGNATURE_COMPLETE, STATE_ENCRYPTION_HANDSHAKE_DATA_PROCESSED, @@ -146,6 +170,15 @@ ProofSource* proof_source_; SignatureCallback* signature_callback_ = nullptr; + // State to handle potentially asynchronous session ticket decryption. + // |ticket_decryption_callback_| points to the non-owned callback that was + // passed to ProofSource::TicketCrypter::Decrypt but hasn't finished running + // yet. + DecryptCallback* ticket_decryption_callback_ = nullptr; + // |decrypted_session_ticket_| contains the decrypted session ticket after the + // callback has run but before it is passed to BoringSSL. + std::vector<uint8_t> decrypted_session_ticket_; + std::string hostname_; std::string cert_verify_sig_; std::unique_ptr<ProofSource::Details> proof_source_details_;
diff --git a/quic/core/tls_server_handshaker_test.cc b/quic/core/tls_server_handshaker_test.cc index 7a30a13..52d5627 100644 --- a/quic/core/tls_server_handshaker_test.cc +++ b/quic/core/tls_server_handshaker_test.cc
@@ -19,6 +19,8 @@ #include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" #include "net/third_party/quiche/src/quic/test_tools/fake_proof_source.h" #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" +#include "net/third_party/quiche/src/quic/test_tools/simple_session_cache.h" +#include "net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h" #include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" @@ -42,15 +44,12 @@ class TlsServerHandshakerTest : public QuicTest { public: TlsServerHandshakerTest() - : proof_source_(new FakeProofSource()), - server_crypto_config_(QuicCryptoServerConfig::TESTING, - QuicRandom::GetInstance(), - std::unique_ptr<ProofSource>(proof_source_), - KeyExchangeSource::Default()), - server_compressed_certs_cache_( + : server_compressed_certs_cache_( QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), server_id_(kServerHostname, kServerPort, false), - client_crypto_config_(crypto_test_utils::ProofVerifierForTesting()) { + client_crypto_config_(crypto_test_utils::ProofVerifierForTesting(), + std::make_unique<test::SimpleSessionCache>()) { + InitializeServerConfig(); InitializeServer(); InitializeFakeClient(); } @@ -64,6 +63,18 @@ alarm_factories_.clear(); } + void InitializeServerConfig() { + SetQuicReloadableFlag(quic_enable_tls_resumption, true); + auto ticket_crypter = std::make_unique<TestTicketCrypter>(); + ticket_crypter_ = ticket_crypter.get(); + auto proof_source = std::make_unique<FakeProofSource>(); + proof_source_ = proof_source.get(); + proof_source_->SetTicketCrypter(std::move(ticket_crypter)); + server_crypto_config_ = std::make_unique<QuicCryptoServerConfig>( + QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(), + std::move(proof_source), KeyExchangeSource::Default()); + } + // Initializes the crypto server stream state for testing. May be // called multiple times. void InitializeServer() { @@ -73,7 +84,7 @@ CreateServerSessionForTest( server_id_, QuicTime::Delta::FromSeconds(100000), supported_versions_, helpers_.back().get(), alarm_factories_.back().get(), - &server_crypto_config_, &server_compressed_certs_cache_, + server_crypto_config_.get(), &server_compressed_certs_cache_, &server_connection_, &server_session); CHECK(server_session); server_session_.reset(server_session); @@ -88,7 +99,7 @@ }); crypto_test_utils::SetupCryptoServerConfigForTest( server_connection_->clock(), server_connection_->random_generator(), - &server_crypto_config_); + server_crypto_config_.get()); } QuicCryptoServerStreamBase* server_stream() { @@ -115,6 +126,7 @@ .WillByDefault(Return(std::vector<std::string>({default_alpn}))); CHECK(client_session); client_session_.reset(client_session); + moved_messages_counts_ = {0, 0}; } void CompleteCryptoHandshake() { @@ -185,8 +197,9 @@ // Server state. PacketSavingConnection* server_connection_; std::unique_ptr<TestQuicSpdyServerSession> server_session_; + TestTicketCrypter* ticket_crypter_; // owned by proof_source_ FakeProofSource* proof_source_; // owned by server_crypto_config_ - QuicCryptoServerConfig server_crypto_config_; + std::unique_ptr<QuicCryptoServerConfig> server_crypto_config_; QuicCompressedCertsCache server_compressed_certs_cache_; QuicServerId server_id_; @@ -347,6 +360,79 @@ ExpectHandshakeSuccessful(); } +TEST_F(TlsServerHandshakerTest, Resumption) { + // Do the first handshake + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_FALSE(client_stream()->IsResumption()); + + // Now do another handshake + InitializeServer(); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_TRUE(client_stream()->IsResumption()); +} + +TEST_F(TlsServerHandshakerTest, ResumptionWithAsyncDecryptCallback) { + // Do the first handshake + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + + ticket_crypter_->SetRunCallbacksAsync(true); + // Now do another handshake + InitializeServer(); + InitializeFakeClient(); + + AdvanceHandshakeWithFakeClient(); + // Test that the DecryptCallback will be run asynchronously, and then run it. + ASSERT_EQ(ticket_crypter_->NumPendingCallbacks(), 1u); + ticket_crypter_->RunPendingCallback(0); + + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_TRUE(client_stream()->IsResumption()); +} + +TEST_F(TlsServerHandshakerTest, ResumptionWithFailingDecryptCallback) { + // Do the first handshake + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + + ticket_crypter_->set_fail_decrypt(true); + // Now do another handshake + InitializeServer(); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_FALSE(client_stream()->IsResumption()); +} + +TEST_F(TlsServerHandshakerTest, ResumptionWithFailingAsyncDecryptCallback) { + // Do the first handshake + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + + ticket_crypter_->set_fail_decrypt(true); + ticket_crypter_->SetRunCallbacksAsync(true); + // Now do another handshake + InitializeServer(); + InitializeFakeClient(); + + AdvanceHandshakeWithFakeClient(); + // Test that the DecryptCallback will be run asynchronously, and then run it. + ASSERT_EQ(ticket_crypter_->NumPendingCallbacks(), 1u); + ticket_crypter_->RunPendingCallback(0); + + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_FALSE(client_stream()->IsResumption()); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/test_tools/fake_proof_source.cc b/quic/test_tools/fake_proof_source.cc index 032560e..3763bcc 100644 --- a/quic/test_tools/fake_proof_source.cc +++ b/quic/test_tools/fake_proof_source.cc
@@ -114,9 +114,17 @@ } ProofSource::TicketCrypter* FakeProofSource::SessionTicketCrypter() { + if (ticket_crypter_) { + return ticket_crypter_.get(); + } return delegate_->SessionTicketCrypter(); } +void FakeProofSource::SetTicketCrypter( + std::unique_ptr<TicketCrypter> ticket_crypter) { + ticket_crypter_ = std::move(ticket_crypter); +} + int FakeProofSource::NumPendingCallbacks() const { return pending_ops_.size(); }
diff --git a/quic/test_tools/fake_proof_source.h b/quic/test_tools/fake_proof_source.h index ef7669d..c179f59 100644 --- a/quic/test_tools/fake_proof_source.h +++ b/quic/test_tools/fake_proof_source.h
@@ -15,10 +15,13 @@ namespace quic { namespace test { -// Implementation of ProofSource which delegates to a ProofSourceForTesting, -// except that when the async GetProof is called, it captures the call and -// allows tests to see that a call is pending, which they can then cause to -// complete at a time of their choosing. +// Implementation of ProofSource which delegates to a ProofSourceForTesting, but +// allows for overriding certain functionality. FakeProofSource allows +// intercepting calls to GetProof and ComputeTlsSignature to force them to run +// asynchronously, and allow the caller to see that the call is pending and +// resume the operation at the caller's choosing. FakeProofSource also allows +// the caller to replace the TicketCrypter provided by +// FakeProofSource::SessionTicketCrypter. class FakeProofSource : public ProofSource { public: FakeProofSource(); @@ -46,9 +49,12 @@ uint16_t signature_algorithm, quiche::QuicheStringPiece in, std::unique_ptr<ProofSource::SignatureCallback> callback) override; - TicketCrypter* SessionTicketCrypter() override; + // Sets the TicketCrypter to use. If nullptr, the TicketCrypter from + // ProofSourceForTesting will be returned instead. + void SetTicketCrypter(std::unique_ptr<TicketCrypter> ticket_crypter); + // Get the number of callbacks which are pending int NumPendingCallbacks() const; @@ -58,6 +64,7 @@ private: std::unique_ptr<ProofSource> delegate_; + std::unique_ptr<TicketCrypter> ticket_crypter_; bool active_ = false; class PendingOp {
diff --git a/quic/test_tools/test_ticket_crypter.cc b/quic/test_tools/test_ticket_crypter.cc new file mode 100644 index 0000000..4d0d93e --- /dev/null +++ b/quic/test_tools/test_ticket_crypter.cc
@@ -0,0 +1,74 @@ +// Copyright (c) 2020 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 "net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h" + +#include <cstring> + +#include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h" + +namespace quic { +namespace test { + +namespace { + +// A TicketCrypter implementation is supposed to encrypt and decrypt session +// tickets. However, the only requirement that is needed of a test +// implementation is that calling Decrypt(Encrypt(input), callback) results in +// callback being called with input. (The output of Encrypt must also not exceed +// the overhead specified by MaxOverhead.) This test implementation encrypts +// tickets by prepending kTicketPrefix to generate the ciphertext. The decrypt +// function checks that the prefix is present and strips it; otherwise it +// returns an empty vector to signal failure. +constexpr char kTicketPrefix[] = "TEST TICKET"; + +} // namespace + +size_t TestTicketCrypter::MaxOverhead() { + return QUICHE_ARRAYSIZE(kTicketPrefix); +} + +std::vector<uint8_t> TestTicketCrypter::Encrypt(quiche::QuicheStringPiece in) { + size_t prefix_len = QUICHE_ARRAYSIZE(kTicketPrefix); + std::vector<uint8_t> out(prefix_len + in.size()); + memcpy(out.data(), kTicketPrefix, prefix_len); + memcpy(out.data() + prefix_len, in.data(), in.size()); + return out; +} + +std::vector<uint8_t> TestTicketCrypter::Decrypt(quiche::QuicheStringPiece in) { + size_t prefix_len = QUICHE_ARRAYSIZE(kTicketPrefix); + if (fail_decrypt_ || in.size() < prefix_len || + memcmp(kTicketPrefix, in.data(), prefix_len) != 0) { + return std::vector<uint8_t>(); + } + return std::vector<uint8_t>(in.begin() + prefix_len, in.end()); +} + +void TestTicketCrypter::Decrypt( + quiche::QuicheStringPiece in, + std::unique_ptr<ProofSource::DecryptCallback> callback) { + auto decrypted_ticket = Decrypt(in); + if (run_async_) { + pending_callbacks_.push_back({std::move(callback), decrypted_ticket}); + } else { + callback->Run(decrypted_ticket); + } +} + +void TestTicketCrypter::SetRunCallbacksAsync(bool run_async) { + run_async_ = run_async; +} + +size_t TestTicketCrypter::NumPendingCallbacks() { + return pending_callbacks_.size(); +} + +void TestTicketCrypter::RunPendingCallback(size_t n) { + const PendingCallback& callback = pending_callbacks_[n]; + callback.callback->Run(callback.decrypted_ticket); +} + +} // namespace test +} // namespace quic
diff --git a/quic/test_tools/test_ticket_crypter.h b/quic/test_tools/test_ticket_crypter.h new file mode 100644 index 0000000..b596348 --- /dev/null +++ b/quic/test_tools/test_ticket_crypter.h
@@ -0,0 +1,49 @@ +// Copyright (c) 2020 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. + +#ifndef QUICHE_QUIC_TEST_TOOLS_TEST_TICKET_CRYPTER_H_ +#define QUICHE_QUIC_TEST_TOOLS_TEST_TICKET_CRYPTER_H_ + +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" + +namespace quic { +namespace test { + +// Provides a simple implementation of ProofSource::TicketCrypter for testing. +// THIS IMPLEMENTATION IS NOT SECURE. It is only intended for testing purposes. +class TestTicketCrypter : public ProofSource::TicketCrypter { + public: + ~TestTicketCrypter() override = default; + + // TicketCrypter interface + size_t MaxOverhead() override; + std::vector<uint8_t> Encrypt(quiche::QuicheStringPiece in) override; + void Decrypt(quiche::QuicheStringPiece in, + std::unique_ptr<ProofSource::DecryptCallback> callback) override; + + void SetRunCallbacksAsync(bool run_async); + size_t NumPendingCallbacks(); + void RunPendingCallback(size_t n); + + // Allows configuring this TestTicketCrypter to fail decryption. + void set_fail_decrypt(bool fail_decrypt) { fail_decrypt_ = fail_decrypt; } + + private: + // Performs the Decrypt operation synchronously. + std::vector<uint8_t> Decrypt(quiche::QuicheStringPiece in); + + struct PendingCallback { + std::unique_ptr<ProofSource::DecryptCallback> callback; + std::vector<uint8_t> decrypted_ticket; + }; + + bool fail_decrypt_ = false; + bool run_async_ = false; + std::vector<PendingCallback> pending_callbacks_; +}; + +} // namespace test +} // namespace quic + +#endif // QUICHE_QUIC_TEST_TOOLS_TEST_TICKET_CRYPTER_H_