Commit 15443b7f authored by rtenneti@chromium.org's avatar rtenneti@chromium.org

This change introduces a way to tie source address token keys to specific QUIC

server configs, so that server can replace both the server config and
source address token key when communication with keystore succeeds.

Add retry logic and more graceful fallback to code that loads QUIC
insecure secrets from keystore.  Not flag protected.

Merge internal change: 63497296

  + Changed ComputeSourceAddressTokenKey to DeriveSourceAddressTokenKey
  + Fixed comments.
  + Changed Config* to a const reference in BuildRejection,
    NewSourceAddressToken and ValidateSourceAddressToken methods.

Merge internal change: 65382861

Use QuicEncrypter and QuicDecrypter with "AES128 + GCM-12" as the AEAD
algorithm to implement CryptoSecretBoxes' Box and Unbox methods. These
methods are used in unit tests only.

R=wtc@chromium.org

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266491 0039d316-1c4b-4281-b951-d872f2087c98
parent e3623994
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
#include "net/quic/crypto/crypto_secret_boxer.h" #include "net/quic/crypto/crypto_secret_boxer.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/scoped_ptr.h" #include "net/quic/crypto/crypto_protocol.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "net/quic/crypto/quic_random.h" #include "net/quic/crypto/quic_random.h"
using base::StringPiece; using base::StringPiece;
...@@ -19,21 +17,48 @@ namespace net { ...@@ -19,21 +17,48 @@ namespace net {
static const size_t kKeySize = 16; static const size_t kKeySize = 16;
// kBoxNonceSize contains the number of bytes of nonce that we use in each box. // kBoxNonceSize contains the number of bytes of nonce that we use in each box.
static const size_t kBoxNonceSize = 16; // TODO(rtenneti): Add support for kBoxNonceSize to be 16 bytes.
//
// From agl@:
// 96-bit nonces are on the edge. An attacker who can collect 2^41
// source-address tokens has a 1% chance of finding a duplicate.
//
// The "average" DDoS is now 32.4M PPS. That's 2^25 source-address tokens
// per second. So one day of that DDoS botnot would reach the 1% mark.
//
// It's not terrible, but it's not a "forget about it" margin.
static const size_t kBoxNonceSize = 12;
CryptoSecretBoxer::CryptoSecretBoxer()
: encrypter_(QuicEncrypter::Create(kAESG)),
decrypter_(QuicDecrypter::Create(kAESG)) {
}
CryptoSecretBoxer::~CryptoSecretBoxer() {}
// static // static
size_t CryptoSecretBoxer::GetKeySize() { return kKeySize; } size_t CryptoSecretBoxer::GetKeySize() { return kKeySize; }
void CryptoSecretBoxer::SetKey(StringPiece key) { bool CryptoSecretBoxer::SetKey(StringPiece key) {
DCHECK_EQ(static_cast<size_t>(kKeySize), key.size()); DCHECK_EQ(static_cast<size_t>(kKeySize), key.size());
key_ = key.as_string(); string key_string = key.as_string();
if (!encrypter_->SetKey(key_string)) {
DLOG(DFATAL) << "CryptoSecretBoxer's encrypter_->SetKey failed.";
return false;
}
if (!decrypter_->SetKey(key_string)) {
DLOG(DFATAL) << "CryptoSecretBoxer's decrypter_->SetKey failed.";
return false;
}
return true;
} }
// TODO(rtenneti): Delete sha256 based code. Use Aes128Gcm12Encrypter to Box the
// plaintext. This is temporary solution for tests to pass.
string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const { string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const {
DCHECK_EQ(kKeySize, encrypter_->GetKeySize());
size_t ciphertext_size = encrypter_->GetCiphertextSize(plaintext.length());
string ret; string ret;
const size_t len = kBoxNonceSize + plaintext.size() + crypto::kSHA256Length; const size_t len = kBoxNonceSize + ciphertext_size;
ret.resize(len); ret.resize(len);
char* data = &ret[0]; char* data = &ret[0];
...@@ -41,49 +66,38 @@ string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const { ...@@ -41,49 +66,38 @@ string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const {
rand->RandBytes(data, kBoxNonceSize); rand->RandBytes(data, kBoxNonceSize);
memcpy(data + kBoxNonceSize, plaintext.data(), plaintext.size()); memcpy(data + kBoxNonceSize, plaintext.data(), plaintext.size());
// Compute sha256 for nonce + plaintext. if (!encrypter_->Encrypt(StringPiece(data, kBoxNonceSize), StringPiece(),
scoped_ptr<crypto::SecureHash> sha256(crypto::SecureHash::Create( plaintext, reinterpret_cast<unsigned char*>(
crypto::SecureHash::SHA256)); data + kBoxNonceSize))) {
sha256->Update(data, kBoxNonceSize + plaintext.size()); DLOG(DFATAL) << "CryptoSecretBoxer's Encrypt failed.";
sha256->Finish(data + kBoxNonceSize + plaintext.size(), return string();
crypto::kSHA256Length); }
return ret; return ret;
} }
// TODO(rtenneti): Delete sha256 based code. Use Aes128Gcm12Decrypter to Unbox
// the plaintext. This is temporary solution for tests to pass.
bool CryptoSecretBoxer::Unbox(StringPiece ciphertext, bool CryptoSecretBoxer::Unbox(StringPiece ciphertext,
string* out_storage, string* out_storage,
StringPiece* out) const { StringPiece* out) const {
if (ciphertext.size() < kBoxNonceSize + crypto::kSHA256Length) { if (ciphertext.size() < kBoxNonceSize) {
return false; return false;
} }
const size_t plaintext_len = char nonce[kBoxNonceSize];
ciphertext.size() - kBoxNonceSize - crypto::kSHA256Length; memcpy(nonce, ciphertext.data(), kBoxNonceSize);
out_storage->resize(plaintext_len); ciphertext.remove_prefix(kBoxNonceSize);
char* data = const_cast<char*>(out_storage->data());
// Copy plaintext from ciphertext.
if (plaintext_len != 0) {
memcpy(data, ciphertext.data() + kBoxNonceSize, plaintext_len);
}
// Compute sha256 for nonce + plaintext. size_t len = ciphertext.size();
scoped_ptr<crypto::SecureHash> sha256(crypto::SecureHash::Create( out_storage->resize(len);
crypto::SecureHash::SHA256)); char* data = const_cast<char*>(out_storage->data());
sha256->Update(ciphertext.data(), ciphertext.size() - crypto::kSHA256Length);
char sha256_bytes[crypto::kSHA256Length];
sha256->Finish(sha256_bytes, sizeof(sha256_bytes));
// Verify sha256. if (!decrypter_->Decrypt(StringPiece(nonce, kBoxNonceSize), StringPiece(),
if (0 != memcmp(ciphertext.data() + ciphertext.size() - crypto::kSHA256Length, ciphertext, reinterpret_cast<unsigned char*>(data),
sha256_bytes, crypto::kSHA256Length)) { &len)) {
return false; return false;
} }
out->set(data, plaintext_len); out->set(data, len);
return true; return true;
} }
......
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
#include <string> #include <string>
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "net/base/net_export.h" #include "net/base/net_export.h"
#include "net/quic/crypto/quic_decrypter.h"
#include "net/quic/crypto/quic_encrypter.h"
namespace net { namespace net {
...@@ -19,12 +22,17 @@ class QuicRandom; ...@@ -19,12 +22,17 @@ class QuicRandom;
// thread-safe. // thread-safe.
class NET_EXPORT_PRIVATE CryptoSecretBoxer { class NET_EXPORT_PRIVATE CryptoSecretBoxer {
public: public:
// Initializes |encrypter_| and |decrypter_| data members.
CryptoSecretBoxer();
~CryptoSecretBoxer();
// GetKeySize returns the number of bytes in a key. // GetKeySize returns the number of bytes in a key.
static size_t GetKeySize(); static size_t GetKeySize();
// SetKey sets the key for this object. This must be done before |Box| or // SetKey sets the key for this object. This must be done before |Box| or
// |Unbox| are called. |key| must be |GetKeySize()| bytes long. // |Unbox| are called. |key| must be |GetKeySize()| bytes long. Returns false
void SetKey(base::StringPiece key); // if |encrypter_| or |decrypter_|'s SetKey method fails.
bool SetKey(base::StringPiece key);
// Box encrypts |plaintext| using a random nonce generated from |rand| and // Box encrypts |plaintext| using a random nonce generated from |rand| and
// returns the resulting ciphertext. Since an authenticator and nonce are // returns the resulting ciphertext. Since an authenticator and nonce are
...@@ -41,7 +49,10 @@ class NET_EXPORT_PRIVATE CryptoSecretBoxer { ...@@ -41,7 +49,10 @@ class NET_EXPORT_PRIVATE CryptoSecretBoxer {
base::StringPiece* out) const; base::StringPiece* out) const;
private: private:
std::string key_; scoped_ptr<QuicEncrypter> encrypter_;
scoped_ptr<QuicDecrypter> decrypter_;
DISALLOW_COPY_AND_ASSIGN(CryptoSecretBoxer);
}; };
} // namespace net } // namespace net
......
...@@ -61,7 +61,7 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf { ...@@ -61,7 +61,7 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
} }
void set_config(base::StringPiece config) { void set_config(base::StringPiece config) {
config_ = config.as_string(); config.CopyToString(&config_);
} }
QuicServerConfigProtobuf::PrivateKey* add_key() { QuicServerConfigProtobuf::PrivateKey* add_key() {
...@@ -89,7 +89,7 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf { ...@@ -89,7 +89,7 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
return priority_ > 0; return priority_ > 0;
} }
int64 priority() const { uint64 priority() const {
return priority_; return priority_;
} }
...@@ -97,6 +97,20 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf { ...@@ -97,6 +97,20 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
priority_ = priority; priority_ = priority;
} }
bool has_source_address_token_secret_override() const {
return !source_address_token_secret_override_.empty();
}
std::string source_address_token_secret_override() const {
return source_address_token_secret_override_;
}
void set_source_address_token_secret_override(
base::StringPiece source_address_token_secret_override) {
source_address_token_secret_override.CopyToString(
&source_address_token_secret_override_);
}
private: private:
std::vector<PrivateKey*> keys_; std::vector<PrivateKey*> keys_;
...@@ -112,6 +126,11 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf { ...@@ -112,6 +126,11 @@ class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
// primary config. // primary config.
uint64 priority_; uint64 priority_;
// Optional override to the secret used to box/unbox source address
// tokens when talking to clients that select this server config.
// It can be of any length as it is fed into a KDF before use.
std::string source_address_token_secret_override_;
DISALLOW_COPY_AND_ASSIGN(QuicServerConfigProtobuf); DISALLOW_COPY_AND_ASSIGN(QuicServerConfigProtobuf);
}; };
......
...@@ -323,8 +323,20 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ...@@ -323,8 +323,20 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
// Smaller numbers mean higher priority. // Smaller numbers mean higher priority.
uint64 priority; uint64 priority;
// source_address_token_boxer_ is used to protect the
// source-address tokens that are given to clients.
// Points to either source_address_token_boxer_storage or the
// default boxer provided by QuicCryptoServerConfig.
const CryptoSecretBoxer* source_address_token_boxer;
// Holds the override source_address_token_boxer instance if the
// Config is not using the default source address token boxer
// instance provided by QuicCryptoServerConfig.
scoped_ptr<CryptoSecretBoxer> source_address_token_boxer_storage;
private: private:
friend class base::RefCounted<Config>; friend class base::RefCounted<Config>;
virtual ~Config(); virtual ~Config();
DISALLOW_COPY_AND_ASSIGN(Config); DISALLOW_COPY_AND_ASSIGN(Config);
...@@ -332,6 +344,10 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ...@@ -332,6 +344,10 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
typedef std::map<ServerConfigID, scoped_refptr<Config> > ConfigMap; typedef std::map<ServerConfigID, scoped_refptr<Config> > ConfigMap;
// Get a ref to the config with a given server config id.
scoped_refptr<Config> GetConfigWithScid(
base::StringPiece requested_scid) const;
// ConfigPrimaryTimeLessThan returns true if a->primary_time < // ConfigPrimaryTimeLessThan returns true if a->primary_time <
// b->primary_time. // b->primary_time.
static bool ConfigPrimaryTimeLessThan(const scoped_refptr<Config>& a, static bool ConfigPrimaryTimeLessThan(const scoped_refptr<Config>& a,
...@@ -346,12 +362,13 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ...@@ -346,12 +362,13 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
// written to |info|. // written to |info|.
void EvaluateClientHello( void EvaluateClientHello(
const uint8* primary_orbit, const uint8* primary_orbit,
scoped_refptr<Config> requested_config,
ValidateClientHelloResultCallback::Result* client_hello_state, ValidateClientHelloResultCallback::Result* client_hello_state,
ValidateClientHelloResultCallback* done_cb) const; ValidateClientHelloResultCallback* done_cb) const;
// BuildRejection sets |out| to be a REJ message in reply to |client_hello|. // BuildRejection sets |out| to be a REJ message in reply to |client_hello|.
void BuildRejection( void BuildRejection(
const scoped_refptr<Config>& config, const Config& config,
const CryptoHandshakeMessage& client_hello, const CryptoHandshakeMessage& client_hello,
const ClientHelloInfo& info, const ClientHelloInfo& info,
QuicRandom* rand, QuicRandom* rand,
...@@ -364,16 +381,20 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ...@@ -364,16 +381,20 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
// NewSourceAddressToken returns a fresh source address token for the given // NewSourceAddressToken returns a fresh source address token for the given
// IP address. // IP address.
std::string NewSourceAddressToken(const IPEndPoint& ip, std::string NewSourceAddressToken(
QuicRandom* rand, const Config& config,
QuicWallTime now) const; const IPEndPoint& ip,
QuicRandom* rand,
QuicWallTime now) const;
// ValidateSourceAddressToken returns true if the source address token in // ValidateSourceAddressToken returns true if the source address token in
// |token| is a valid and timely token for the IP address |ip| given that the // |token| is a valid and timely token for the IP address |ip| given that the
// current time is |now|. // current time is |now|.
bool ValidateSourceAddressToken(base::StringPiece token, bool ValidateSourceAddressToken(
const IPEndPoint& ip, const Config& config,
QuicWallTime now) const; base::StringPiece token,
const IPEndPoint& ip,
QuicWallTime now) const;
// NewServerNonce generates and encrypts a random nonce. // NewServerNonce generates and encrypts a random nonce.
std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const; std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const;
...@@ -413,9 +434,10 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig { ...@@ -413,9 +434,10 @@ class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
// observed client nonces in order to prevent replay attacks. // observed client nonces in order to prevent replay attacks.
mutable scoped_ptr<StrikeRegisterClient> strike_register_client_; mutable scoped_ptr<StrikeRegisterClient> strike_register_client_;
// source_address_token_boxer_ is used to protect the source-address tokens // Default source_address_token_boxer_ used to protect the
// that are given to clients. // source-address tokens that are given to clients. Individual
CryptoSecretBoxer source_address_token_boxer_; // configs may use boxers with alternate secrets.
CryptoSecretBoxer default_source_address_token_boxer_;
// server_nonce_boxer_ is used to encrypt and validate suggested server // server_nonce_boxer_ is used to encrypt and validate suggested server
// nonces. // nonces.
......
...@@ -32,16 +32,37 @@ class QuicCryptoServerConfigPeer { ...@@ -32,16 +32,37 @@ class QuicCryptoServerConfigPeer {
explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config) explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config)
: server_config_(server_config) {} : server_config_(server_config) {}
string NewSourceAddressToken(IPEndPoint ip, scoped_refptr<QuicCryptoServerConfig::Config> GetConfig(string config_id) {
QuicRandom* rand, base::AutoLock locked(server_config_->configs_lock_);
QuicWallTime now) { if (config_id == "<primary>") {
return server_config_->NewSourceAddressToken(ip, rand, now); return scoped_refptr<QuicCryptoServerConfig::Config>(
server_config_->primary_config_);
} else {
return server_config_->GetConfigWithScid(config_id);
}
}
bool ConfigHasDefaultSourceAddressTokenBoxer(string config_id) {
scoped_refptr<QuicCryptoServerConfig::Config> config = GetConfig(config_id);
return config->source_address_token_boxer ==
&(server_config_->default_source_address_token_boxer_);
} }
bool ValidateSourceAddressToken(StringPiece srct, string NewSourceAddressToken(
string config_id,
IPEndPoint ip,
QuicRandom* rand,
QuicWallTime now) {
return server_config_->NewSourceAddressToken(
*GetConfig(config_id), ip, rand, now);
}
bool ValidateSourceAddressToken(string config_id,
StringPiece srct,
IPEndPoint ip, IPEndPoint ip,
QuicWallTime now) { QuicWallTime now) {
return server_config_->ValidateSourceAddressToken(srct, ip, now); return server_config_->ValidateSourceAddressToken(
*GetConfig(config_id), srct, ip, now);
} }
base::Lock* GetStrikeRegisterClientLock() { base::Lock* GetStrikeRegisterClientLock() {
...@@ -209,31 +230,77 @@ TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) { ...@@ -209,31 +230,77 @@ TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) {
} }
TEST(QuicCryptoServerConfigTest, SourceAddressTokens) { TEST(QuicCryptoServerConfigTest, SourceAddressTokens) {
const string kPrimary = "<primary>";
const string kOverride = "Config with custom source address token key";
MockClock clock;
clock.AdvanceTime(QuicTime::Delta::FromSeconds(1000000));
QuicWallTime now = clock.WallNow();
const QuicWallTime original_time = now;
QuicRandom* rand = QuicRandom::GetInstance(); QuicRandom* rand = QuicRandom::GetInstance();
QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
QuicCryptoServerConfigPeer peer(&server);
scoped_ptr<CryptoHandshakeMessage>(
server.AddDefaultConfig(rand, &clock,
QuicCryptoServerConfig::ConfigOptions()));
// Add a config that overrides the default boxer.
QuicCryptoServerConfig::ConfigOptions options;
options.id = kOverride;
scoped_ptr<QuicServerConfigProtobuf> protobuf(
QuicCryptoServerConfig::GenerateConfig(rand, &clock, options));
protobuf->set_source_address_token_secret_override("a secret key");
// Lower priority than the default config.
protobuf->set_priority(1);
scoped_ptr<CryptoHandshakeMessage>(
server.AddConfig(protobuf.get(), now));
EXPECT_TRUE(peer.ConfigHasDefaultSourceAddressTokenBoxer(kPrimary));
EXPECT_FALSE(peer.ConfigHasDefaultSourceAddressTokenBoxer(kOverride));
IPAddressNumber ip; IPAddressNumber ip;
CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip)); CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
IPEndPoint ip4 = IPEndPoint(ip, 1); IPEndPoint ip4 = IPEndPoint(ip, 1);
CHECK(ParseIPLiteralToNumber("2001:db8:0::42", &ip)); CHECK(ParseIPLiteralToNumber("2001:db8:0::42", &ip));
IPEndPoint ip6 = IPEndPoint(ip, 2); IPEndPoint ip6 = IPEndPoint(ip, 2);
MockClock clock;
clock.AdvanceTime(QuicTime::Delta::FromSeconds(1000000));
QuicCryptoServerConfigPeer peer(&server);
QuicWallTime now = clock.WallNow();
const QuicWallTime original_time = now;
const string token4 = peer.NewSourceAddressToken(ip4, rand, now);
const string token6 = peer.NewSourceAddressToken(ip6, rand, now);
EXPECT_TRUE(peer.ValidateSourceAddressToken(token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip6, now));
EXPECT_TRUE(peer.ValidateSourceAddressToken(token6, ip6, now));
// Primary config generates configs that validate successfully.
const string token4 = peer.NewSourceAddressToken(kPrimary, ip4, rand, now);
const string token6 = peer.NewSourceAddressToken(kPrimary, ip6, rand, now);
EXPECT_TRUE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip6, now));
EXPECT_TRUE(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(
kOverride, override_token4, ip4, now));
EXPECT_FALSE(peer.ValidateSourceAddressToken(
kOverride, override_token4, ip6, now));
EXPECT_TRUE(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));
// Validation fails after tokens expire.
now = original_time.Add(QuicTime::Delta::FromSeconds(86400 * 7)); now = original_time.Add(QuicTime::Delta::FromSeconds(86400 * 7));
EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip4, now)); EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
now = original_time.Subtract(QuicTime::Delta::FromSeconds(3600 * 2)); now = original_time.Subtract(QuicTime::Delta::FromSeconds(3600 * 2));
EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip4, now)); EXPECT_FALSE(peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
} }
class CryptoServerConfigsTest : public ::testing::Test { class CryptoServerConfigsTest : public ::testing::Test {
......
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