| // Copyright (c) 2012 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/crypto_test_utils.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "third_party/boringssl/src/include/openssl/bn.h" |
| #include "third_party/boringssl/src/include/openssl/ec.h" |
| #include "third_party/boringssl/src/include/openssl/ecdsa.h" |
| #include "third_party/boringssl/src/include/openssl/nid.h" |
| #include "third_party/boringssl/src/include/openssl/sha.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/channel_id.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/common_cert_set.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_decrypter.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" |
| #include "net/third_party/quiche/src/quic/core/proto/crypto_server_config.pb.h" |
| #include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h" |
| #include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h" |
| #include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" |
| #include "net/third_party/quiche/src/quic/core/quic_server_id.h" |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" |
| #include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" |
| #include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/simple_quic_framer.h" |
| |
| namespace quic { |
| namespace test { |
| |
| TestChannelIDKey::TestChannelIDKey(EVP_PKEY* ecdsa_key) |
| : ecdsa_key_(ecdsa_key) {} |
| TestChannelIDKey::~TestChannelIDKey() {} |
| |
| bool TestChannelIDKey::Sign(QuicStringPiece signed_data, |
| std::string* out_signature) const { |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| if (EVP_DigestSignInit(md_ctx.get(), nullptr, EVP_sha256(), nullptr, |
| ecdsa_key_.get()) != 1) { |
| return false; |
| } |
| |
| EVP_DigestUpdate(md_ctx.get(), ChannelIDVerifier::kContextStr, |
| strlen(ChannelIDVerifier::kContextStr) + 1); |
| EVP_DigestUpdate(md_ctx.get(), ChannelIDVerifier::kClientToServerStr, |
| strlen(ChannelIDVerifier::kClientToServerStr) + 1); |
| EVP_DigestUpdate(md_ctx.get(), signed_data.data(), signed_data.size()); |
| |
| size_t sig_len; |
| if (!EVP_DigestSignFinal(md_ctx.get(), nullptr, &sig_len)) { |
| return false; |
| } |
| |
| std::unique_ptr<uint8_t[]> der_sig(new uint8_t[sig_len]); |
| if (!EVP_DigestSignFinal(md_ctx.get(), der_sig.get(), &sig_len)) { |
| return false; |
| } |
| |
| uint8_t* derp = der_sig.get(); |
| bssl::UniquePtr<ECDSA_SIG> sig( |
| d2i_ECDSA_SIG(nullptr, const_cast<const uint8_t**>(&derp), sig_len)); |
| if (sig.get() == nullptr) { |
| return false; |
| } |
| |
| // The signature consists of a pair of 32-byte numbers. |
| static const size_t kSignatureLength = 32 * 2; |
| std::unique_ptr<uint8_t[]> signature(new uint8_t[kSignatureLength]); |
| if (!BN_bn2bin_padded(&signature[0], 32, sig->r) || |
| !BN_bn2bin_padded(&signature[32], 32, sig->s)) { |
| return false; |
| } |
| |
| *out_signature = |
| std::string(reinterpret_cast<char*>(signature.get()), kSignatureLength); |
| |
| return true; |
| } |
| |
| std::string TestChannelIDKey::SerializeKey() const { |
| // i2d_PublicKey will produce an ANSI X9.62 public key which, for a P-256 |
| // key, is 0x04 (meaning uncompressed) followed by the x and y field |
| // elements as 32-byte, big-endian numbers. |
| static const int kExpectedKeyLength = 65; |
| |
| int len = i2d_PublicKey(ecdsa_key_.get(), nullptr); |
| if (len != kExpectedKeyLength) { |
| return ""; |
| } |
| |
| uint8_t buf[kExpectedKeyLength]; |
| uint8_t* derp = buf; |
| i2d_PublicKey(ecdsa_key_.get(), &derp); |
| |
| return std::string(reinterpret_cast<char*>(buf + 1), kExpectedKeyLength - 1); |
| } |
| |
| TestChannelIDSource::~TestChannelIDSource() {} |
| |
| QuicAsyncStatus TestChannelIDSource::GetChannelIDKey( |
| const std::string& hostname, |
| std::unique_ptr<ChannelIDKey>* channel_id_key, |
| ChannelIDSourceCallback* /*callback*/) { |
| *channel_id_key = QuicMakeUnique<TestChannelIDKey>(HostnameToKey(hostname)); |
| return QUIC_SUCCESS; |
| } |
| |
| // static |
| EVP_PKEY* TestChannelIDSource::HostnameToKey(const std::string& hostname) { |
| // In order to generate a deterministic key for a given hostname the |
| // hostname is hashed with SHA-256 and the resulting digest is treated as a |
| // big-endian number. The most-significant bit is cleared to ensure that |
| // the resulting value is less than the order of the group and then it's |
| // taken as a private key. Given the private key, the public key is |
| // calculated with a group multiplication. |
| SHA256_CTX sha256; |
| SHA256_Init(&sha256); |
| SHA256_Update(&sha256, hostname.data(), hostname.size()); |
| |
| unsigned char digest[SHA256_DIGEST_LENGTH]; |
| SHA256_Final(digest, &sha256); |
| |
| // Ensure that the digest is less than the order of the P-256 group by |
| // clearing the most-significant bit. |
| digest[0] &= 0x7f; |
| |
| bssl::UniquePtr<BIGNUM> k(BN_new()); |
| CHECK(BN_bin2bn(digest, sizeof(digest), k.get()) != nullptr); |
| |
| bssl::UniquePtr<EC_GROUP> p256( |
| EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); |
| CHECK(p256); |
| |
| bssl::UniquePtr<EC_KEY> ecdsa_key(EC_KEY_new()); |
| CHECK(ecdsa_key && EC_KEY_set_group(ecdsa_key.get(), p256.get())); |
| |
| bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); |
| CHECK(EC_POINT_mul(p256.get(), point.get(), k.get(), nullptr, nullptr, |
| nullptr)); |
| |
| EC_KEY_set_private_key(ecdsa_key.get(), k.get()); |
| EC_KEY_set_public_key(ecdsa_key.get(), point.get()); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); |
| // EVP_PKEY_set1_EC_KEY takes a reference so no |release| here. |
| EVP_PKEY_set1_EC_KEY(pkey.get(), ecdsa_key.get()); |
| |
| return pkey.release(); |
| } |
| |
| namespace crypto_test_utils { |
| |
| namespace { |
| |
| // CryptoFramerVisitor is a framer visitor that records handshake messages. |
| class CryptoFramerVisitor : public CryptoFramerVisitorInterface { |
| public: |
| CryptoFramerVisitor() : error_(false) {} |
| |
| void OnError(CryptoFramer* framer) override { error_ = true; } |
| |
| void OnHandshakeMessage(const CryptoHandshakeMessage& message) override { |
| messages_.push_back(message); |
| } |
| |
| bool error() const { return error_; } |
| |
| const std::vector<CryptoHandshakeMessage>& messages() const { |
| return messages_; |
| } |
| |
| private: |
| bool error_; |
| std::vector<CryptoHandshakeMessage> messages_; |
| }; |
| |
| // HexChar parses |c| as a hex character. If valid, it sets |*value| to the |
| // value of the hex character and returns true. Otherwise it returns false. |
| bool HexChar(char c, uint8_t* value) { |
| if (c >= '0' && c <= '9') { |
| *value = c - '0'; |
| return true; |
| } |
| if (c >= 'a' && c <= 'f') { |
| *value = c - 'a' + 10; |
| return true; |
| } |
| if (c >= 'A' && c <= 'F') { |
| *value = c - 'A' + 10; |
| return true; |
| } |
| return false; |
| } |
| |
| // A ChannelIDSource that works in asynchronous mode unless the |callback| |
| // argument to GetChannelIDKey is nullptr. |
| class AsyncTestChannelIDSource : public ChannelIDSource, public CallbackSource { |
| public: |
| // Takes ownership of |sync_source|, a synchronous ChannelIDSource. |
| explicit AsyncTestChannelIDSource(ChannelIDSource* sync_source) |
| : sync_source_(sync_source) {} |
| ~AsyncTestChannelIDSource() override {} |
| |
| // ChannelIDSource implementation. |
| QuicAsyncStatus GetChannelIDKey(const std::string& hostname, |
| std::unique_ptr<ChannelIDKey>* channel_id_key, |
| ChannelIDSourceCallback* callback) override { |
| // Synchronous mode. |
| if (!callback) { |
| return sync_source_->GetChannelIDKey(hostname, channel_id_key, nullptr); |
| } |
| |
| // Asynchronous mode. |
| QuicAsyncStatus status = |
| sync_source_->GetChannelIDKey(hostname, &channel_id_key_, nullptr); |
| if (status != QUIC_SUCCESS) { |
| return QUIC_FAILURE; |
| } |
| callback_.reset(callback); |
| return QUIC_PENDING; |
| } |
| |
| // CallbackSource implementation. |
| void RunPendingCallbacks() override { |
| if (callback_) { |
| callback_->Run(&channel_id_key_); |
| callback_.reset(); |
| } |
| } |
| |
| private: |
| std::unique_ptr<ChannelIDSource> sync_source_; |
| std::unique_ptr<ChannelIDSourceCallback> callback_; |
| std::unique_ptr<ChannelIDKey> channel_id_key_; |
| }; |
| |
| } // anonymous namespace |
| |
| FakeServerOptions::FakeServerOptions() {} |
| |
| FakeServerOptions::~FakeServerOptions() {} |
| |
| FakeClientOptions::FakeClientOptions() |
| : channel_id_enabled(false), channel_id_source_async(false) {} |
| |
| FakeClientOptions::~FakeClientOptions() {} |
| |
| namespace { |
| // This class is used by GenerateFullCHLO() to extract SCID and STK from |
| // REJ/SREJ and to construct a full CHLO with these fields and given inchoate |
| // CHLO. |
| class FullChloGenerator { |
| public: |
| FullChloGenerator( |
| QuicCryptoServerConfig* crypto_config, |
| QuicSocketAddress server_addr, |
| QuicSocketAddress client_addr, |
| const QuicClock* clock, |
| QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| CryptoHandshakeMessage* out) |
| : crypto_config_(crypto_config), |
| server_addr_(server_addr), |
| client_addr_(client_addr), |
| clock_(clock), |
| signed_config_(signed_config), |
| compressed_certs_cache_(compressed_certs_cache), |
| out_(out), |
| params_(new QuicCryptoNegotiatedParameters) {} |
| |
| class ValidateClientHelloCallback : public ValidateClientHelloResultCallback { |
| public: |
| explicit ValidateClientHelloCallback(FullChloGenerator* generator) |
| : generator_(generator) {} |
| void Run(QuicReferenceCountedPointer< |
| ValidateClientHelloResultCallback::Result> result, |
| std::unique_ptr<ProofSource::Details> /* details */) override { |
| generator_->ValidateClientHelloDone(std::move(result)); |
| } |
| |
| private: |
| FullChloGenerator* generator_; |
| }; |
| |
| std::unique_ptr<ValidateClientHelloCallback> |
| GetValidateClientHelloCallback() { |
| return QuicMakeUnique<ValidateClientHelloCallback>(this); |
| } |
| |
| private: |
| void ValidateClientHelloDone( |
| QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result> |
| result) { |
| result_ = result; |
| crypto_config_->ProcessClientHello( |
| result_, /*reject_only=*/false, TestConnectionId(1), server_addr_, |
| client_addr_, AllSupportedVersions().front(), AllSupportedVersions(), |
| /*use_stateless_rejects=*/true, |
| /*server_designated_connection_id=*/TestConnectionId(2), clock_, |
| QuicRandom::GetInstance(), compressed_certs_cache_, params_, |
| signed_config_, /*total_framing_overhead=*/50, kDefaultMaxPacketSize, |
| GetProcessClientHelloCallback()); |
| } |
| |
| class ProcessClientHelloCallback : public ProcessClientHelloResultCallback { |
| public: |
| explicit ProcessClientHelloCallback(FullChloGenerator* generator) |
| : generator_(generator) {} |
| void Run( |
| QuicErrorCode error, |
| const std::string& error_details, |
| std::unique_ptr<CryptoHandshakeMessage> message, |
| std::unique_ptr<DiversificationNonce> diversification_nonce, |
| std::unique_ptr<ProofSource::Details> proof_source_details) override { |
| generator_->ProcessClientHelloDone(std::move(message)); |
| } |
| |
| private: |
| FullChloGenerator* generator_; |
| }; |
| |
| std::unique_ptr<ProcessClientHelloCallback> GetProcessClientHelloCallback() { |
| return QuicMakeUnique<ProcessClientHelloCallback>(this); |
| } |
| |
| void ProcessClientHelloDone(std::unique_ptr<CryptoHandshakeMessage> rej) { |
| // Verify output is a REJ or SREJ. |
| EXPECT_THAT(rej->tag(), |
| testing::AnyOf(testing::Eq(kSREJ), testing::Eq(kREJ))); |
| |
| VLOG(1) << "Extract valid STK and SCID from\n" << rej->DebugString(); |
| QuicStringPiece srct; |
| ASSERT_TRUE(rej->GetStringPiece(kSourceAddressTokenTag, &srct)); |
| |
| QuicStringPiece scfg; |
| ASSERT_TRUE(rej->GetStringPiece(kSCFG, &scfg)); |
| std::unique_ptr<CryptoHandshakeMessage> server_config( |
| CryptoFramer::ParseMessage(scfg)); |
| |
| QuicStringPiece scid; |
| ASSERT_TRUE(server_config->GetStringPiece(kSCID, &scid)); |
| |
| *out_ = result_->client_hello; |
| out_->SetStringPiece(kSCID, scid); |
| out_->SetStringPiece(kSourceAddressTokenTag, srct); |
| uint64_t xlct = LeafCertHashForTesting(); |
| out_->SetValue(kXLCT, xlct); |
| } |
| |
| protected: |
| QuicCryptoServerConfig* crypto_config_; |
| QuicSocketAddress server_addr_; |
| QuicSocketAddress client_addr_; |
| const QuicClock* clock_; |
| QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config_; |
| QuicCompressedCertsCache* compressed_certs_cache_; |
| CryptoHandshakeMessage* out_; |
| |
| QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_; |
| QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result> |
| result_; |
| }; |
| |
| } // namespace |
| |
| int HandshakeWithFakeServer(QuicConfig* server_quic_config, |
| MockQuicConnectionHelper* helper, |
| MockAlarmFactory* alarm_factory, |
| PacketSavingConnection* client_conn, |
| QuicCryptoClientStream* client, |
| const FakeServerOptions& options) { |
| PacketSavingConnection* server_conn = new PacketSavingConnection( |
| helper, alarm_factory, Perspective::IS_SERVER, |
| ParsedVersionOfIndex(client_conn->supported_versions(), 0)); |
| |
| QuicCryptoServerConfig crypto_config( |
| QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(), |
| ProofSourceForTesting(), KeyExchangeSource::Default(), |
| TlsServerHandshaker::CreateSslCtx()); |
| QuicCompressedCertsCache compressed_certs_cache( |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); |
| SetupCryptoServerConfigForTest(server_conn->clock(), |
| server_conn->random_generator(), |
| &crypto_config, options); |
| |
| TestQuicSpdyServerSession server_session( |
| server_conn, *server_quic_config, client_conn->supported_versions(), |
| &crypto_config, &compressed_certs_cache); |
| server_session.OnSuccessfulVersionNegotiation( |
| client_conn->supported_versions().front()); |
| EXPECT_CALL(*server_session.helper(), |
| CanAcceptClientHello(testing::_, testing::_, testing::_, |
| testing::_, testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*server_session.helper(), |
| GenerateConnectionIdForReject(testing::_, testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*server_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| |
| // The client's handshake must have been started already. |
| CHECK_NE(0u, client_conn->encrypted_packets_.size()); |
| |
| CommunicateHandshakeMessages(client_conn, client, server_conn, |
| server_session.GetMutableCryptoStream()); |
| CompareClientAndServerKeys(client, server_session.GetMutableCryptoStream()); |
| |
| return client->num_sent_client_hellos(); |
| } |
| |
| int HandshakeWithFakeClient(MockQuicConnectionHelper* helper, |
| MockAlarmFactory* alarm_factory, |
| PacketSavingConnection* server_conn, |
| QuicCryptoServerStream* server, |
| const QuicServerId& server_id, |
| const FakeClientOptions& options) { |
| ParsedQuicVersionVector supported_versions = AllSupportedVersions(); |
| if (options.only_tls_versions) { |
| supported_versions.clear(); |
| for (QuicTransportVersion transport_version : |
| AllSupportedTransportVersions()) { |
| supported_versions.push_back( |
| ParsedQuicVersion(PROTOCOL_TLS1_3, transport_version)); |
| } |
| } |
| PacketSavingConnection* client_conn = new PacketSavingConnection( |
| helper, alarm_factory, Perspective::IS_CLIENT, supported_versions); |
| // Advance the time, because timers do not like uninitialized times. |
| client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(1)); |
| |
| QuicCryptoClientConfig crypto_config(ProofVerifierForTesting(), |
| TlsClientHandshaker::CreateSslCtx()); |
| AsyncTestChannelIDSource* async_channel_id_source = nullptr; |
| if (options.channel_id_enabled) { |
| ChannelIDSource* source = ChannelIDSourceForTesting(); |
| if (options.channel_id_source_async) { |
| async_channel_id_source = new AsyncTestChannelIDSource(source); |
| source = async_channel_id_source; |
| } |
| crypto_config.SetChannelIDSource(source); |
| } |
| if (!options.token_binding_params.empty()) { |
| crypto_config.tb_key_params = options.token_binding_params; |
| } |
| TestQuicSpdyClientSession client_session(client_conn, DefaultQuicConfig(), |
| supported_versions, server_id, |
| &crypto_config); |
| |
| EXPECT_CALL(client_session, OnProofValid(testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(client_session, OnProofVerifyDetailsAvailable(testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| client_session.GetMutableCryptoStream()->CryptoConnect(); |
| CHECK_EQ(1u, client_conn->encrypted_packets_.size()); |
| |
| CommunicateHandshakeMessagesAndRunCallbacks( |
| client_conn, client_session.GetMutableCryptoStream(), server_conn, server, |
| async_channel_id_source); |
| |
| if (server->handshake_confirmed() && server->encryption_established()) { |
| CompareClientAndServerKeys(client_session.GetMutableCryptoStream(), server); |
| |
| if (options.channel_id_enabled) { |
| std::unique_ptr<ChannelIDKey> channel_id_key; |
| QuicAsyncStatus status = |
| crypto_config.channel_id_source()->GetChannelIDKey( |
| server_id.host(), &channel_id_key, nullptr); |
| EXPECT_EQ(QUIC_SUCCESS, status); |
| EXPECT_EQ(channel_id_key->SerializeKey(), |
| server->crypto_negotiated_params().channel_id); |
| EXPECT_EQ( |
| options.channel_id_source_async, |
| client_session.GetCryptoStream()->WasChannelIDSourceCallbackRun()); |
| } |
| } |
| |
| return client_session.GetCryptoStream()->num_sent_client_hellos(); |
| } |
| |
| void SetupCryptoServerConfigForTest(const QuicClock* clock, |
| QuicRandom* rand, |
| QuicCryptoServerConfig* crypto_config, |
| const FakeServerOptions& fake_options) { |
| QuicCryptoServerConfig::ConfigOptions options; |
| options.channel_id_enabled = true; |
| options.token_binding_params = fake_options.token_binding_params; |
| std::unique_ptr<CryptoHandshakeMessage> scfg( |
| crypto_config->AddDefaultConfig(rand, clock, options)); |
| } |
| |
| void SendHandshakeMessageToStream(QuicCryptoStream* stream, |
| const CryptoHandshakeMessage& message, |
| Perspective perspective) { |
| const QuicData& data = message.GetSerialized(); |
| QuicSession* session = QuicStreamPeer::session(stream); |
| if (session->connection()->transport_version() < QUIC_VERSION_47) { |
| QuicStreamFrame frame(QuicUtils::GetCryptoStreamId( |
| session->connection()->transport_version()), |
| false, stream->crypto_bytes_read(), |
| data.AsStringPiece()); |
| stream->OnStreamFrame(frame); |
| } else { |
| EncryptionLevel level = session->connection()->last_decrypted_level(); |
| QuicCryptoFrame frame(level, stream->BytesReadOnLevel(level), |
| data.AsStringPiece()); |
| stream->OnCryptoFrame(frame); |
| } |
| } |
| |
| void CommunicateHandshakeMessages(PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server) { |
| CommunicateHandshakeMessagesAndRunCallbacks(client_conn, client, server_conn, |
| server, nullptr); |
| } |
| |
| void CommunicateHandshakeMessagesAndRunCallbacks( |
| PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server, |
| CallbackSource* callback_source) { |
| size_t client_i = 0, server_i = 0; |
| while (!client->handshake_confirmed() || !server->handshake_confirmed()) { |
| ASSERT_GT(client_conn->encrypted_packets_.size(), client_i); |
| QUIC_LOG(INFO) << "Processing " |
| << client_conn->encrypted_packets_.size() - client_i |
| << " packets client->server"; |
| MovePackets(client_conn, &client_i, server, server_conn, |
| Perspective::IS_SERVER); |
| if (callback_source) { |
| callback_source->RunPendingCallbacks(); |
| } |
| |
| if (client->handshake_confirmed() && server->handshake_confirmed()) { |
| break; |
| } |
| ASSERT_GT(server_conn->encrypted_packets_.size(), server_i); |
| QUIC_LOG(INFO) << "Processing " |
| << server_conn->encrypted_packets_.size() - server_i |
| << " packets server->client"; |
| MovePackets(server_conn, &server_i, client, client_conn, |
| Perspective::IS_CLIENT); |
| if (callback_source) { |
| callback_source->RunPendingCallbacks(); |
| } |
| } |
| } |
| |
| std::pair<size_t, size_t> AdvanceHandshake(PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| size_t client_i, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server, |
| size_t server_i) { |
| QUIC_LOG(INFO) << "Processing " |
| << client_conn->encrypted_packets_.size() - client_i |
| << " packets client->server"; |
| MovePackets(client_conn, &client_i, server, server_conn, |
| Perspective::IS_SERVER); |
| |
| QUIC_LOG(INFO) << "Processing " |
| << server_conn->encrypted_packets_.size() - server_i |
| << " packets server->client"; |
| if (server_conn->encrypted_packets_.size() - server_i == 2) { |
| QUIC_LOG(INFO) << "here"; |
| } |
| MovePackets(server_conn, &server_i, client, client_conn, |
| Perspective::IS_CLIENT); |
| |
| return std::make_pair(client_i, server_i); |
| } |
| |
| std::string GetValueForTag(const CryptoHandshakeMessage& message, QuicTag tag) { |
| auto it = message.tag_value_map().find(tag); |
| if (it == message.tag_value_map().end()) { |
| return std::string(); |
| } |
| return it->second; |
| } |
| |
| uint64_t LeafCertHashForTesting() { |
| QuicReferenceCountedPointer<ProofSource::Chain> chain; |
| QuicSocketAddress server_address(QuicIpAddress::Any4(), 42); |
| QuicCryptoProof proof; |
| std::unique_ptr<ProofSource> proof_source(ProofSourceForTesting()); |
| |
| class Callback : public ProofSource::Callback { |
| public: |
| Callback(bool* ok, QuicReferenceCountedPointer<ProofSource::Chain>* chain) |
| : ok_(ok), chain_(chain) {} |
| |
| void Run(bool ok, |
| const QuicReferenceCountedPointer<ProofSource::Chain>& chain, |
| const QuicCryptoProof& /* proof */, |
| std::unique_ptr<ProofSource::Details> /* details */) override { |
| *ok_ = ok; |
| *chain_ = chain; |
| } |
| |
| private: |
| bool* ok_; |
| QuicReferenceCountedPointer<ProofSource::Chain>* chain_; |
| }; |
| |
| // Note: relies on the callback being invoked synchronously |
| bool ok = false; |
| proof_source->GetProof( |
| server_address, "", "", AllSupportedTransportVersions().front(), "", |
| std::unique_ptr<ProofSource::Callback>(new Callback(&ok, &chain))); |
| if (!ok || chain->certs.empty()) { |
| DCHECK(false) << "Proof generation failed"; |
| return 0; |
| } |
| |
| return QuicUtils::FNV1a_64_Hash(chain->certs.at(0)); |
| } |
| |
| class MockCommonCertSets : public CommonCertSets { |
| public: |
| MockCommonCertSets(QuicStringPiece cert, uint64_t hash, uint32_t index) |
| : cert_(cert), hash_(hash), index_(index) {} |
| |
| QuicStringPiece GetCommonHashes() const override { |
| QUIC_BUG << "not implemented"; |
| return QuicStringPiece(); |
| } |
| |
| QuicStringPiece GetCert(uint64_t hash, uint32_t index) const override { |
| if (hash == hash_ && index == index_) { |
| return cert_; |
| } |
| return QuicStringPiece(); |
| } |
| |
| bool MatchCert(QuicStringPiece cert, |
| QuicStringPiece common_set_hashes, |
| uint64_t* out_hash, |
| uint32_t* out_index) const override { |
| if (cert != cert_) { |
| return false; |
| } |
| |
| if (common_set_hashes.size() % sizeof(uint64_t) != 0) { |
| return false; |
| } |
| bool client_has_set = false; |
| for (size_t i = 0; i < common_set_hashes.size(); i += sizeof(uint64_t)) { |
| uint64_t hash; |
| memcpy(&hash, common_set_hashes.data() + i, sizeof(hash)); |
| if (hash == hash_) { |
| client_has_set = true; |
| break; |
| } |
| } |
| |
| if (!client_has_set) { |
| return false; |
| } |
| |
| *out_hash = hash_; |
| *out_index = index_; |
| return true; |
| } |
| |
| private: |
| const std::string cert_; |
| const uint64_t hash_; |
| const uint32_t index_; |
| }; |
| |
| CommonCertSets* MockCommonCertSets(QuicStringPiece cert, |
| uint64_t hash, |
| uint32_t index) { |
| return new class MockCommonCertSets(cert, hash, index); |
| } |
| |
| void FillInDummyReject(CryptoHandshakeMessage* rej, bool reject_is_stateless) { |
| if (reject_is_stateless) { |
| rej->set_tag(kSREJ); |
| } else { |
| rej->set_tag(kREJ); |
| } |
| |
| // Minimum SCFG that passes config validation checks. |
| // clang-format off |
| unsigned char scfg[] = { |
| // SCFG |
| 0x53, 0x43, 0x46, 0x47, |
| // num entries |
| 0x01, 0x00, |
| // padding |
| 0x00, 0x00, |
| // EXPY |
| 0x45, 0x58, 0x50, 0x59, |
| // EXPY end offset |
| 0x08, 0x00, 0x00, 0x00, |
| // Value |
| '1', '2', '3', '4', |
| '5', '6', '7', '8' |
| }; |
| // clang-format on |
| rej->SetValue(kSCFG, scfg); |
| rej->SetStringPiece(kServerNonceTag, "SERVER_NONCE"); |
| int64_t ttl = 2 * 24 * 60 * 60; |
| rej->SetValue(kSTTL, ttl); |
| std::vector<QuicTag> reject_reasons; |
| reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE); |
| rej->SetVector(kRREJ, reject_reasons); |
| } |
| |
| void CompareClientAndServerKeys(QuicCryptoClientStream* client, |
| QuicCryptoServerStream* server) { |
| QuicFramer* client_framer = QuicConnectionPeer::GetFramer( |
| QuicStreamPeer::session(client)->connection()); |
| QuicFramer* server_framer = QuicConnectionPeer::GetFramer( |
| QuicStreamPeer::session(server)->connection()); |
| const QuicEncrypter* client_encrypter( |
| QuicFramerPeer::GetEncrypter(client_framer, ENCRYPTION_ZERO_RTT)); |
| const QuicDecrypter* client_decrypter( |
| QuicStreamPeer::session(client)->connection()->decrypter()); |
| const QuicEncrypter* client_forward_secure_encrypter( |
| QuicFramerPeer::GetEncrypter(client_framer, ENCRYPTION_FORWARD_SECURE)); |
| const QuicDecrypter* client_forward_secure_decrypter( |
| QuicStreamPeer::session(client)->connection()->alternative_decrypter()); |
| const QuicEncrypter* server_encrypter( |
| QuicFramerPeer::GetEncrypter(server_framer, ENCRYPTION_ZERO_RTT)); |
| const QuicDecrypter* server_decrypter( |
| QuicStreamPeer::session(server)->connection()->decrypter()); |
| const QuicEncrypter* server_forward_secure_encrypter( |
| QuicFramerPeer::GetEncrypter(server_framer, ENCRYPTION_FORWARD_SECURE)); |
| const QuicDecrypter* server_forward_secure_decrypter( |
| QuicStreamPeer::session(server)->connection()->alternative_decrypter()); |
| |
| QuicStringPiece client_encrypter_key = client_encrypter->GetKey(); |
| QuicStringPiece client_encrypter_iv = client_encrypter->GetNoncePrefix(); |
| QuicStringPiece client_decrypter_key = client_decrypter->GetKey(); |
| QuicStringPiece client_decrypter_iv = client_decrypter->GetNoncePrefix(); |
| QuicStringPiece client_forward_secure_encrypter_key = |
| client_forward_secure_encrypter->GetKey(); |
| QuicStringPiece client_forward_secure_encrypter_iv = |
| client_forward_secure_encrypter->GetNoncePrefix(); |
| QuicStringPiece client_forward_secure_decrypter_key = |
| client_forward_secure_decrypter->GetKey(); |
| QuicStringPiece client_forward_secure_decrypter_iv = |
| client_forward_secure_decrypter->GetNoncePrefix(); |
| QuicStringPiece server_encrypter_key = server_encrypter->GetKey(); |
| QuicStringPiece server_encrypter_iv = server_encrypter->GetNoncePrefix(); |
| QuicStringPiece server_decrypter_key = server_decrypter->GetKey(); |
| QuicStringPiece server_decrypter_iv = server_decrypter->GetNoncePrefix(); |
| QuicStringPiece server_forward_secure_encrypter_key = |
| server_forward_secure_encrypter->GetKey(); |
| QuicStringPiece server_forward_secure_encrypter_iv = |
| server_forward_secure_encrypter->GetNoncePrefix(); |
| QuicStringPiece server_forward_secure_decrypter_key = |
| server_forward_secure_decrypter->GetKey(); |
| QuicStringPiece server_forward_secure_decrypter_iv = |
| server_forward_secure_decrypter->GetNoncePrefix(); |
| |
| QuicStringPiece client_subkey_secret = |
| client->crypto_negotiated_params().subkey_secret; |
| QuicStringPiece server_subkey_secret = |
| server->crypto_negotiated_params().subkey_secret; |
| |
| const char kSampleLabel[] = "label"; |
| const char kSampleContext[] = "context"; |
| const size_t kSampleOutputLength = 32; |
| std::string client_key_extraction; |
| std::string server_key_extraction; |
| std::string client_tb_ekm; |
| std::string server_tb_ekm; |
| EXPECT_TRUE(client->ExportKeyingMaterial(kSampleLabel, kSampleContext, |
| kSampleOutputLength, |
| &client_key_extraction)); |
| EXPECT_TRUE(server->ExportKeyingMaterial(kSampleLabel, kSampleContext, |
| kSampleOutputLength, |
| &server_key_extraction)); |
| |
| CompareCharArraysWithHexError("client write key", client_encrypter_key.data(), |
| client_encrypter_key.length(), |
| server_decrypter_key.data(), |
| server_decrypter_key.length()); |
| CompareCharArraysWithHexError("client write IV", client_encrypter_iv.data(), |
| client_encrypter_iv.length(), |
| server_decrypter_iv.data(), |
| server_decrypter_iv.length()); |
| CompareCharArraysWithHexError("server write key", server_encrypter_key.data(), |
| server_encrypter_key.length(), |
| client_decrypter_key.data(), |
| client_decrypter_key.length()); |
| CompareCharArraysWithHexError("server write IV", server_encrypter_iv.data(), |
| server_encrypter_iv.length(), |
| client_decrypter_iv.data(), |
| client_decrypter_iv.length()); |
| CompareCharArraysWithHexError("client forward secure write key", |
| client_forward_secure_encrypter_key.data(), |
| client_forward_secure_encrypter_key.length(), |
| server_forward_secure_decrypter_key.data(), |
| server_forward_secure_decrypter_key.length()); |
| CompareCharArraysWithHexError("client forward secure write IV", |
| client_forward_secure_encrypter_iv.data(), |
| client_forward_secure_encrypter_iv.length(), |
| server_forward_secure_decrypter_iv.data(), |
| server_forward_secure_decrypter_iv.length()); |
| CompareCharArraysWithHexError("server forward secure write key", |
| server_forward_secure_encrypter_key.data(), |
| server_forward_secure_encrypter_key.length(), |
| client_forward_secure_decrypter_key.data(), |
| client_forward_secure_decrypter_key.length()); |
| CompareCharArraysWithHexError("server forward secure write IV", |
| server_forward_secure_encrypter_iv.data(), |
| server_forward_secure_encrypter_iv.length(), |
| client_forward_secure_decrypter_iv.data(), |
| client_forward_secure_decrypter_iv.length()); |
| CompareCharArraysWithHexError("subkey secret", client_subkey_secret.data(), |
| client_subkey_secret.length(), |
| server_subkey_secret.data(), |
| server_subkey_secret.length()); |
| CompareCharArraysWithHexError( |
| "sample key extraction", client_key_extraction.data(), |
| client_key_extraction.length(), server_key_extraction.data(), |
| server_key_extraction.length()); |
| |
| CompareCharArraysWithHexError("token binding key extraction", |
| client_tb_ekm.data(), client_tb_ekm.length(), |
| server_tb_ekm.data(), server_tb_ekm.length()); |
| } |
| |
| QuicTag ParseTag(const char* tagstr) { |
| const size_t len = strlen(tagstr); |
| CHECK_NE(0u, len); |
| |
| QuicTag tag = 0; |
| |
| if (tagstr[0] == '#') { |
| CHECK_EQ(static_cast<size_t>(1 + 2 * 4), len); |
| tagstr++; |
| |
| for (size_t i = 0; i < 8; i++) { |
| tag <<= 4; |
| |
| uint8_t v = 0; |
| CHECK(HexChar(tagstr[i], &v)); |
| tag |= v; |
| } |
| |
| return tag; |
| } |
| |
| CHECK_LE(len, 4u); |
| for (size_t i = 0; i < 4; i++) { |
| tag >>= 8; |
| if (i < len) { |
| tag |= static_cast<uint32_t>(tagstr[i]) << 24; |
| } |
| } |
| |
| return tag; |
| } |
| |
| CryptoHandshakeMessage CreateCHLO( |
| std::vector<std::pair<std::string, std::string>> tags_and_values) { |
| return CreateCHLO(tags_and_values, -1); |
| } |
| |
| CryptoHandshakeMessage CreateCHLO( |
| std::vector<std::pair<std::string, std::string>> tags_and_values, |
| int minimum_size_bytes) { |
| CryptoHandshakeMessage msg; |
| msg.set_tag(MakeQuicTag('C', 'H', 'L', 'O')); |
| |
| if (minimum_size_bytes > 0) { |
| msg.set_minimum_size(minimum_size_bytes); |
| } |
| |
| for (const auto& tag_and_value : tags_and_values) { |
| const std::string& tag = tag_and_value.first; |
| const std::string& value = tag_and_value.second; |
| |
| const QuicTag quic_tag = ParseTag(tag.c_str()); |
| |
| size_t value_len = value.length(); |
| if (value_len > 0 && value[0] == '#') { |
| // This is ascii encoded hex. |
| std::string hex_value = |
| QuicTextUtils::HexDecode(QuicStringPiece(&value[1])); |
| msg.SetStringPiece(quic_tag, hex_value); |
| continue; |
| } |
| msg.SetStringPiece(quic_tag, value); |
| } |
| |
| // The CryptoHandshakeMessage needs to be serialized and parsed to ensure |
| // that any padding is included. |
| std::unique_ptr<QuicData> bytes(CryptoFramer::ConstructHandshakeMessage(msg)); |
| std::unique_ptr<CryptoHandshakeMessage> parsed( |
| CryptoFramer::ParseMessage(bytes->AsStringPiece())); |
| CHECK(parsed); |
| |
| return *parsed; |
| } |
| |
| ChannelIDSource* ChannelIDSourceForTesting() { |
| return new TestChannelIDSource(); |
| } |
| |
| void MovePackets(PacketSavingConnection* source_conn, |
| size_t* inout_packet_index, |
| QuicCryptoStream* dest_stream, |
| PacketSavingConnection* dest_conn, |
| Perspective dest_perspective) { |
| SimpleQuicFramer framer(source_conn->supported_versions(), dest_perspective); |
| |
| SimpleQuicFramer null_encryption_framer(source_conn->supported_versions(), |
| dest_perspective); |
| |
| size_t index = *inout_packet_index; |
| for (; index < source_conn->encrypted_packets_.size(); index++) { |
| // In order to properly test the code we need to perform encryption and |
| // decryption so that the crypters latch when expected. The crypters are in |
| // |dest_conn|, but we don't want to try and use them there. Instead we swap |
| // them into |framer|, perform the decryption with them, and then swap ther |
| // back. |
| QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer()); |
| if (!framer.ProcessPacket(*source_conn->encrypted_packets_[index])) { |
| // The framer will be unable to decrypt forward-secure packets sent after |
| // the handshake is complete. Don't treat them as handshake packets. |
| break; |
| } |
| QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer()); |
| dest_conn->OnDecryptedPacket(framer.last_decrypted_level()); |
| |
| if (dest_stream->handshake_protocol() == PROTOCOL_TLS1_3) { |
| // Try to process the packet with a framer that only has the NullDecrypter |
| // for decryption. If ProcessPacket succeeds, that means the packet was |
| // encrypted with the NullEncrypter. With the TLS handshaker in use, no |
| // packets should ever be encrypted with the NullEncrypter, instead |
| // they're encrypted with an obfuscation cipher based on QUIC version and |
| // connection ID. |
| ASSERT_FALSE(null_encryption_framer.ProcessPacket( |
| *source_conn->encrypted_packets_[index])) |
| << "No TLS packets should be encrypted with the NullEncrypter"; |
| } |
| |
| // Since we're using QuicFramers separate from the connections to move |
| // packets, the QuicConnection never gets notified about what level the last |
| // packet was decrypted at. This is needed by TLS to know what encryption |
| // level was used for the data it's receiving, so we plumb this information |
| // from the SimpleQuicFramer back into the connection. |
| dest_conn->OnDecryptedPacket(framer.last_decrypted_level()); |
| |
| QuicConnectionPeer::SetCurrentPacket( |
| dest_conn, source_conn->encrypted_packets_[index]->AsStringPiece()); |
| for (const auto& stream_frame : framer.stream_frames()) { |
| dest_stream->OnStreamFrame(*stream_frame); |
| } |
| for (const auto& crypto_frame : framer.crypto_frames()) { |
| dest_stream->OnCryptoFrame(*crypto_frame); |
| } |
| } |
| *inout_packet_index = index; |
| |
| QuicConnectionPeer::SetCurrentPacket(dest_conn, QuicStringPiece(nullptr, 0)); |
| } |
| |
| CryptoHandshakeMessage GenerateDefaultInchoateCHLO( |
| const QuicClock* clock, |
| QuicTransportVersion version, |
| QuicCryptoServerConfig* crypto_config) { |
| // clang-format off |
| return CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"AEAD", "AESG"}, |
| {"KEXS", "C255"}, |
| {"PUBS", GenerateClientPublicValuesHex().c_str()}, |
| {"NONC", GenerateClientNonceHex(clock, crypto_config).c_str()}, |
| {"VER\0", QuicVersionLabelToString( |
| QuicVersionToQuicVersionLabel(version)).c_str()}}, |
| kClientHelloMinimumSize); |
| // clang-format on |
| } |
| |
| std::string GenerateClientNonceHex(const QuicClock* clock, |
| QuicCryptoServerConfig* crypto_config) { |
| QuicCryptoServerConfig::ConfigOptions old_config_options; |
| QuicCryptoServerConfig::ConfigOptions new_config_options; |
| old_config_options.id = "old-config-id"; |
| delete crypto_config->AddDefaultConfig(QuicRandom::GetInstance(), clock, |
| old_config_options); |
| std::unique_ptr<QuicServerConfigProtobuf> primary_config( |
| crypto_config->GenerateConfig(QuicRandom::GetInstance(), clock, |
| new_config_options)); |
| primary_config->set_primary_time(clock->WallNow().ToUNIXSeconds()); |
| std::unique_ptr<CryptoHandshakeMessage> msg( |
| crypto_config->AddConfig(std::move(primary_config), clock->WallNow())); |
| QuicStringPiece orbit; |
| CHECK(msg->GetStringPiece(kORBT, &orbit)); |
| std::string nonce; |
| CryptoUtils::GenerateNonce(clock->WallNow(), QuicRandom::GetInstance(), orbit, |
| &nonce); |
| return ("#" + QuicTextUtils::HexEncode(nonce)); |
| } |
| |
| std::string GenerateClientPublicValuesHex() { |
| char public_value[32]; |
| memset(public_value, 42, sizeof(public_value)); |
| return ("#" + QuicTextUtils::HexEncode(public_value, sizeof(public_value))); |
| } |
| |
| void GenerateFullCHLO(const CryptoHandshakeMessage& inchoate_chlo, |
| QuicCryptoServerConfig* crypto_config, |
| QuicSocketAddress server_addr, |
| QuicSocketAddress client_addr, |
| QuicTransportVersion version, |
| const QuicClock* clock, |
| QuicReferenceCountedPointer<QuicSignedServerConfig> proof, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| CryptoHandshakeMessage* out) { |
| // Pass a inchoate CHLO. |
| FullChloGenerator generator(crypto_config, server_addr, client_addr, clock, |
| proof, compressed_certs_cache, out); |
| crypto_config->ValidateClientHello( |
| inchoate_chlo, client_addr.host(), server_addr, version, clock, proof, |
| generator.GetValidateClientHelloCallback()); |
| } |
| |
| } // namespace crypto_test_utils |
| } // namespace test |
| } // namespace quic |