Commit 19886aaf authored by rtenneti@chromium.org's avatar rtenneti@chromium.org

QUIC Crypto - return the reasons for reject message. Reject reason

contains failures due to invalid source address token client nonce and
server nonce. Will add UMA histogram in chrome when this CL is merged.

Send reject reason to client for debugging purposes. Protected behind
new flags: FLAGS_use_early_return_when_verifying_chlo and
FLAGS_send_quic_crypto_reject_reason

Merge internal change: 69227401

R=jar@chromium.org, wtc@chromium.org, avd@google.com

Review URL: https://codereview.chromium.org/331143006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278523 0039d316-1c4b-4281-b951-d872f2087c98
parent 209a4cfc
......@@ -110,6 +110,9 @@ const QuicTag kRSEQ = TAG('R', 'S', 'E', 'Q'); // Rejected sequence number
// Universal tags
const QuicTag kPAD = TAG('P', 'A', 'D', '\0'); // Padding
// Reasons for server sending rejection message tag.
const QuicTag kRREJ = TAG('R', 'R', 'E', 'J');
// These tags have a special form so that they appear either at the beginning
// or the end of a handshake message. Since handshake messages are sorted by
// tag value, the tags with 0 at the end will sort first and those with 255 at
......
This diff is collapsed.
......@@ -587,6 +587,17 @@ QuicErrorCode QuicCryptoClientConfig::ProcessRejection(
}
}
const QuicTag* reject_reasons;
size_t num_reject_reasons;
if (rej.GetTaglist(kRREJ, &reject_reasons,
&num_reject_reasons) == QUIC_NO_ERROR) {
#if defined(DEBUG)
for (size_t i = 0; i < num_reject_reasons; ++i) {
DVLOG(1) << "Reasons for rejection: " << reject_reasons[i];
}
#endif
}
return QUIC_NO_ERROR;
}
......
......@@ -33,6 +33,7 @@
#include "net/quic/crypto/strike_register.h"
#include "net/quic/crypto/strike_register_client.h"
#include "net/quic/quic_clock.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_socket_address_coder.h"
#include "net/quic/quic_utils.h"
......@@ -81,6 +82,9 @@ struct ClientHelloInfo {
StringPiece client_nonce;
StringPiece server_nonce;
StringPiece user_agent_id;
// Errors from EvaluateClientHello.
vector<uint32> reject_reasons;
};
struct ValidateClientHelloResultCallback::Result {
......@@ -146,6 +150,12 @@ class VerifyNonceIsValidAndUniqueCallback
virtual void RunImpl(bool nonce_is_valid_and_unique) OVERRIDE {
DVLOG(1) << "Using client nonce, unique: " << nonce_is_valid_and_unique;
result_->info.unique = nonce_is_valid_and_unique;
// TODO(rtenneti): Implement capturing of error from strike register.
// Temporarily treat them as CLIENT_NONCE_UNKNOWN_FAILURE.
if (!nonce_is_valid_and_unique) {
result_->info.reject_reasons.push_back(
static_cast<uint32>(CLIENT_NONCE_UNKNOWN_FAILURE));
}
done_cb_->Run(result_);
}
......@@ -888,32 +898,65 @@ void QuicCryptoServerConfig::EvaluateClientHello(
client_hello.GetStringPiece(kUAID, &info->user_agent_id);
StringPiece srct;
if (requested_config.get() != NULL &&
client_hello.GetStringPiece(kSourceAddressTokenTag, &srct) &&
ValidateSourceAddressToken(*requested_config,
srct,
info->client_ip,
info->now)) {
info->valid_source_address_token = true;
} else {
// No server config with the requested ID, or no valid source address token.
if (!requested_config.get()) {
StringPiece requested_scid;
if (client_hello.GetStringPiece(kSCID, &requested_scid)) {
info->reject_reasons.push_back(
static_cast<uint32>(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE));
} else {
info->reject_reasons.push_back(
static_cast<uint32>(SERVER_CONFIG_INCHOATE_HELLO_FAILURE));
}
// No server config with the requested ID.
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
}
HandshakeFailureReason source_address_token_error;
StringPiece srct;
if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct)) {
source_address_token_error =
ValidateSourceAddressToken(*requested_config,
srct,
info->client_ip,
info->now);
info->valid_source_address_token =
(source_address_token_error == HANDSHAKE_OK);
} else {
source_address_token_error = SOURCE_ADDRESS_TOKEN_INVALID_FAILURE;
}
bool found_error = false;
if (source_address_token_error != HANDSHAKE_OK) {
info->reject_reasons.push_back(
static_cast<uint32>(source_address_token_error));
// No valid source address token.
if (FLAGS_use_early_return_when_verifying_chlo) {
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
}
found_error = true;
}
if (client_hello.GetStringPiece(kNONC, &info->client_nonce) &&
info->client_nonce.size() == kNonceSize) {
info->client_nonce_well_formed = true;
} else {
info->reject_reasons.push_back(
static_cast<uint32>(CLIENT_NONCE_INVALID_FAILURE));
// Invalid client nonce.
DVLOG(1) << "Invalid client nonce.";
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
if (FLAGS_use_early_return_when_verifying_chlo) {
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
}
found_error = true;
}
if (!replay_protection_) {
info->unique = true;
if (!found_error) {
info->unique = true;
}
DVLOG(1) << "No replay protection.";
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
......@@ -922,12 +965,26 @@ void QuicCryptoServerConfig::EvaluateClientHello(
client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce);
if (!info->server_nonce.empty()) {
// If the server nonce is present, use it to establish uniqueness.
info->unique = ValidateServerNonce(info->server_nonce, info->now);
HandshakeFailureReason server_nonce_error =
ValidateServerNonce(info->server_nonce, info->now);
if (server_nonce_error == HANDSHAKE_OK) {
info->unique = true;
} else {
info->reject_reasons.push_back(static_cast<uint32>(server_nonce_error));
info->unique = false;
}
DVLOG(1) << "Using server nonce, unique: " << info->unique;
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
}
// We want to contact strike register if there are no errors because it is
// a RPC call and is expensive.
if (found_error) {
helper.ValidationComplete(QUIC_NO_ERROR, "");
return;
}
// Use the client nonce to establish uniqueness.
StrikeRegisterClient* strike_register_client;
{
......@@ -971,6 +1028,12 @@ void QuicCryptoServerConfig::BuildRejection(
out->SetStringPiece(kServerNonceTag, NewServerNonce(rand, info.now));
}
if (FLAGS_send_quic_crypto_reject_reason) {
// Send client the reject reason for debugging purposes.
DCHECK_LT(0u, info.reject_reasons.size());
out->SetVector(kRREJ, info.reject_reasons);
}
// The client may have requested a certificate chain.
const QuicTag* their_proof_demands;
size_t num_their_proof_demands;
......@@ -1288,7 +1351,7 @@ string QuicCryptoServerConfig::NewSourceAddressToken(const Config& config,
rand, source_address_token.SerializeAsString());
}
bool QuicCryptoServerConfig::ValidateSourceAddressToken(
HandshakeFailureReason QuicCryptoServerConfig::ValidateSourceAddressToken(
const Config& config,
StringPiece token,
const IPEndPoint& ip,
......@@ -1296,13 +1359,13 @@ bool QuicCryptoServerConfig::ValidateSourceAddressToken(
string storage;
StringPiece plaintext;
if (!config.source_address_token_boxer->Unbox(token, &storage, &plaintext)) {
return false;
return SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE;
}
SourceAddressToken source_address_token;
if (!source_address_token.ParseFromArray(plaintext.data(),
plaintext.size())) {
return false;
return SOURCE_ADDRESS_TOKEN_PARSE_FAILURE;
}
IPAddressNumber ip_address = ip.address();
......@@ -1311,7 +1374,7 @@ bool QuicCryptoServerConfig::ValidateSourceAddressToken(
}
if (source_address_token.ip() != IPAddressToPackedString(ip_address)) {
// It's for a different IP address.
return false;
return SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE;
}
const QuicWallTime timestamp(
......@@ -1320,15 +1383,15 @@ bool QuicCryptoServerConfig::ValidateSourceAddressToken(
if (now.IsBefore(timestamp) &&
delta.ToSeconds() > source_address_token_future_secs_) {
return false;
return SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE;
}
if (now.IsAfter(timestamp) &&
delta.ToSeconds() > source_address_token_lifetime_secs_) {
return false;
return SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE;
}
return true;
return HANDSHAKE_OK;
}
// kServerNoncePlaintextSize is the number of bytes in an unencrypted server
......@@ -1354,12 +1417,13 @@ string QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand,
StringPiece(reinterpret_cast<char*>(server_nonce), sizeof(server_nonce)));
}
bool QuicCryptoServerConfig::ValidateServerNonce(StringPiece token,
QuicWallTime now) const {
HandshakeFailureReason QuicCryptoServerConfig::ValidateServerNonce(
StringPiece token,
QuicWallTime now) const {
string storage;
StringPiece plaintext;
if (!server_nonce_boxer_.Unbox(token, &storage, &plaintext)) {
return false;
return SERVER_NONCE_DECRYPTION_FAILURE;
}
// plaintext contains:
......@@ -1369,7 +1433,7 @@ bool QuicCryptoServerConfig::ValidateServerNonce(StringPiece token,
if (plaintext.size() != kServerNoncePlaintextSize) {
// This should never happen because the value decrypted correctly.
LOG(DFATAL) << "Seemingly valid server nonce had incorrect length.";
return false;
return SERVER_NONCE_INVALID_FAILURE;
}
uint8 server_nonce[32];
......@@ -1394,7 +1458,7 @@ bool QuicCryptoServerConfig::ValidateServerNonce(StringPiece token,
server_nonce, static_cast<uint32>(now.ToUNIXSeconds()));
}
return is_unique;
return is_unique ? HANDSHAKE_OK : SERVER_NONCE_NOT_UNIQUE_FAILURE;
}
QuicCryptoServerConfig::Config::Config()
......
......@@ -40,6 +40,32 @@ namespace test {
class QuicCryptoServerConfigPeer;
} // namespace test
enum HandshakeFailureReason {
HANDSHAKE_OK = 0,
// Failure reasons for an invalid client nonce.
// TODO(rtenneti): Implement capturing of error from strike register.
CLIENT_NONCE_UNKNOWN_FAILURE = 100,
CLIENT_NONCE_INVALID_FAILURE,
// Failure reasons for an invalid server nonce.
SERVER_NONCE_INVALID_FAILURE = 200,
SERVER_NONCE_DECRYPTION_FAILURE,
SERVER_NONCE_NOT_UNIQUE_FAILURE,
// Failure reasons for an invalid server config.
SERVER_CONFIG_INCHOATE_HELLO_FAILURE = 300,
SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE,
// Failure reasons for an invalid source adddress token.
SOURCE_ADDRESS_TOKEN_INVALID_FAILURE = 400,
SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
SOURCE_ADDRESS_TOKEN_PARSE_FAILURE,
SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE,
SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE,
};
// Hook that allows application code to subscribe to primary config changes.
class PrimaryConfigChangedCallback {
public:
......@@ -385,13 +411,13 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
QuicRandom* rand,
QuicWallTime now) const;
// ValidateSourceAddressToken returns true if the source address token in
// |token| is a valid and timely token for the IP address |ip| given that the
// current time is |now|.
bool ValidateSourceAddressToken(const Config& config,
base::StringPiece token,
const IPEndPoint& ip,
QuicWallTime now) const;
// ValidateSourceAddressToken returns HANDSHAKE_OK if the source address token
// in |token| is a valid and timely token for the IP address |ip| given that
// the current time is |now|. Otherwise it returns the reason for failure.
HandshakeFailureReason ValidateSourceAddressToken(const Config& config,
base::StringPiece token,
const IPEndPoint& ip,
QuicWallTime now) const;
// NewServerNonce generates and encrypts a random nonce.
std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const;
......@@ -399,10 +425,11 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
// ValidateServerNonce decrypts |token| and verifies that it hasn't been
// previously used and is recent enough that it is plausible that it was part
// of a very recently provided rejection ("recent" will be on the order of
// 10-30 seconds). If so, it records that it has been used and returns true.
// Otherwise it returns false.
bool ValidateServerNonce(base::StringPiece echoed_server_nonce,
QuicWallTime now) const;
// 10-30 seconds). If so, it records that it has been used and returns
// HANDSHAKE_OK. Otherwise it returns the reason for failure.
HandshakeFailureReason ValidateServerNonce(
base::StringPiece echoed_server_nonce,
QuicWallTime now) const;
// replay_protection_ controls whether the server enforces that handshakes
// aren't replays.
......
......@@ -9,6 +9,7 @@
#include "base/stl_util.h"
#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
#include "net/quic/crypto/crypto_handshake_message.h"
#include "net/quic/crypto/crypto_secret_boxer.h"
#include "net/quic/crypto/crypto_server_config_protobuf.h"
#include "net/quic/crypto/quic_random.h"
#include "net/quic/crypto/strike_register_client.h"
......@@ -58,14 +59,23 @@ class QuicCryptoServerConfigPeer {
*GetConfig(config_id), ip, rand, now);
}
bool ValidateSourceAddressToken(string config_id,
StringPiece srct,
IPEndPoint ip,
QuicWallTime now) {
HandshakeFailureReason ValidateSourceAddressToken(string config_id,
StringPiece srct,
IPEndPoint ip,
QuicWallTime now) {
return server_config_->ValidateSourceAddressToken(
*GetConfig(config_id), srct, ip, now);
}
string NewServerNonce(QuicRandom* rand, QuicWallTime now) const {
return server_config_->NewServerNonce(rand, now);
}
HandshakeFailureReason ValidateServerNonce(StringPiece token,
QuicWallTime now) {
return server_config_->ValidateServerNonce(token, now);
}
base::Lock* GetStrikeRegisterClientLock() {
return &server_config_->strike_register_client_lock_;
}
......@@ -270,41 +280,84 @@ TEST(QuicCryptoServerConfigTest, SourceAddressTokens) {
const string token4 = peer.NewSourceAddressToken(kPrimary, ip4, rand, now);
const string token4d = peer.NewSourceAddressToken(kPrimary, ip4d, rand, now);
const string token6 = peer.NewSourceAddressToken(kPrimary, ip6, rand, now);
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4d, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip6, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token4d, ip4, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token4d, ip4d, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4d, ip6, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token6, ip6, now));
EXPECT_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kPrimary, token4, ip4, now));
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kPrimary, token4, ip4d, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, token4, ip6, now));
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kPrimary, token4d, ip4, now));
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kPrimary, token4d, ip4d, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, token4d, ip6, now));
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kPrimary, token6, ip6, now));
// Override config generates configs that validate successfully.
const string override_token4 = peer.NewSourceAddressToken(
kOverride, ip4, rand, now);
const string override_token6 = peer.NewSourceAddressToken(
kOverride, ip6, rand, now);
EXPECT_TRUE(peer.ValidateSourceAddressToken(
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kOverride, override_token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(
kOverride, override_token4, ip6, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
peer.ValidateSourceAddressToken(kOverride, override_token4, ip6,
now));
DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
kOverride, override_token6, ip6, now));
// Tokens generated by the primary config do not validate
// successfully against the override config, and vice versa.
EXPECT_FALSE(peer.ValidateSourceAddressToken(kOverride, token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kOverride, token6, ip6, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(
kPrimary, override_token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(
kPrimary, override_token6, ip6, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
peer.ValidateSourceAddressToken(kOverride, token4, ip4, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
peer.ValidateSourceAddressToken(kOverride, token6, ip6, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, override_token4, ip4,
now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, override_token6, ip6,
now));
// Validation fails after tokens expire.
now = original_time.Add(QuicTime::Delta::FromSeconds(86400 * 7));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
now = original_time.Subtract(QuicTime::Delta::FromSeconds(3600 * 2));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
DCHECK_EQ(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE,
peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
}
TEST(QuicCryptoServerConfigTest, ValidateServerNonce) {
QuicRandom* rand = QuicRandom::GetInstance();
QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
QuicCryptoServerConfigPeer peer(&server);
StringPiece message("hello world");
const size_t key_size = CryptoSecretBoxer::GetKeySize();
scoped_ptr<uint8[]> key(new uint8[key_size]);
memset(key.get(), 0x11, key_size);
CryptoSecretBoxer boxer;
boxer.SetKey(StringPiece(reinterpret_cast<char*>(key.get()), key_size));
const string box = boxer.Box(rand, message);
MockClock clock;
QuicWallTime now = clock.WallNow();
const QuicWallTime original_time = now;
EXPECT_EQ(SERVER_NONCE_DECRYPTION_FAILURE,
peer.ValidateServerNonce(box, now));
string server_nonce = peer.NewServerNonce(rand, now);
EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now));
EXPECT_EQ(SERVER_NONCE_NOT_UNIQUE_FAILURE,
peer.ValidateServerNonce(server_nonce, now));
now = original_time.Add(QuicTime::Delta::FromSeconds(1000 * 7));
server_nonce = peer.NewServerNonce(rand, now);
EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now));
}
class CryptoServerConfigsTest : public ::testing::Test {
......
......@@ -39,3 +39,10 @@ bool FLAGS_quic_use_time_loss_detection = false;
// If true, allow peer port migration of established QUIC connections.
bool FLAGS_quic_allow_port_migration = true;
// If true, it will return as soon as an error is detected while validating
// CHLO.
bool FLAGS_use_early_return_when_verifying_chlo = true;
// If true, QUIC crypto reject message will include the reasons for rejection.
bool FLAGS_send_quic_crypto_reject_reason = false;
......@@ -14,5 +14,7 @@ NET_EXPORT_PRIVATE extern bool FLAGS_enable_quic_connection_flow_control_2;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_allow_oversized_packets_for_test;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_use_time_loss_detection;
NET_EXPORT_PRIVATE extern bool FLAGS_quic_allow_port_migration;
NET_EXPORT_PRIVATE extern bool FLAGS_use_early_return_when_verifying_chlo;
NET_EXPORT_PRIVATE extern bool FLAGS_send_quic_crypto_reject_reason;
#endif // NET_QUIC_QUIC_FLAGS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment