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_