blob: 48280028e469ac4f1f39b48a5740ac3601229800 [file] [log] [blame]
// 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 "components/sync/nigori/nigori.h"
#include <memory>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/tick_clock.h"
#include "components/sync/base/sync_base_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::IsNull;
using testing::NotNull;
class FakeTickClock : public base::TickClock {
public:
FakeTickClock() : call_count_(0) {}
// How much the mock clock advances after each call to the mocked
// base::TimeTicks::Now(). We do this because we are testing functions which
// call NowTicks() twice.
static constexpr base::TimeDelta kTicksAdvanceAfterEachCall =
base::TimeDelta::FromMilliseconds(250);
base::TimeTicks NowTicks() const override {
int current_call_count = call_count_;
++call_count_;
return base::TimeTicks() + current_call_count * kTicksAdvanceAfterEachCall;
}
int call_count() const { return call_count_; }
private:
mutable int call_count_;
};
constexpr base::TimeDelta FakeTickClock::kTicksAdvanceAfterEachCall;
TEST(SyncNigoriTest, Permute) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string permuted;
EXPECT_TRUE(nigori->Permute(Nigori::Password, "test name", &permuted));
std::string expected =
"evpwwPT726JS/mhxv1UwPVLDz5ha/GrMA8HfA3sQGJr5"
"5zFtrFep7DXu9FIyGbBEdlHNWtwVlPVE5FEgyoV++w==";
EXPECT_EQ(expected, permuted);
}
TEST(SyncNigoriTest, PermuteIsConstant) {
std::unique_ptr<Nigori> nigori1 = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori1, NotNull());
std::string permuted1;
EXPECT_TRUE(nigori1->Permute(Nigori::Password, "name", &permuted1));
std::unique_ptr<Nigori> nigori2 = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori2, NotNull());
std::string permuted2;
EXPECT_TRUE(nigori2->Permute(Nigori::Password, "name", &permuted2));
EXPECT_LT(0U, permuted1.size());
EXPECT_EQ(permuted1, permuted2);
}
TEST(SyncNigoriTest, EncryptDifferentIv) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string plaintext("value");
std::string encrypted1;
EXPECT_TRUE(nigori->Encrypt(plaintext, &encrypted1));
std::string encrypted2;
EXPECT_TRUE(nigori->Encrypt(plaintext, &encrypted2));
EXPECT_NE(encrypted1, encrypted2);
}
TEST(SyncNigoriTest, Decrypt) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string encrypted =
"NNYlnzaaLPXWXyzz8J+u4OKgLiKRBPu2GJdjHWk0m3ADZrJhnmer30"
"Zgiy4Ulxlfh6fmS71k8rop+UvSJdL1k/fcNLJ1C6sY5Z86ijyl1Jo=";
std::string plaintext;
EXPECT_TRUE(nigori->Decrypt(encrypted, &plaintext));
std::string expected("test, test, 1, 2, 3");
EXPECT_EQ(expected, plaintext);
}
TEST(SyncNigoriTest, EncryptDecrypt) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string plaintext("value");
std::string encrypted;
EXPECT_TRUE(nigori->Encrypt(plaintext, &encrypted));
std::string decrypted;
EXPECT_TRUE(nigori->Decrypt(encrypted, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
TEST(SyncNigoriTest, CorruptedIv) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string plaintext("test");
std::string encrypted;
EXPECT_TRUE(nigori->Encrypt(plaintext, &encrypted));
// Corrupt the IV by changing one of its byte.
encrypted[0] = (encrypted[0] == 'a' ? 'b' : 'a');
std::string decrypted;
EXPECT_TRUE(nigori->Decrypt(encrypted, &decrypted));
EXPECT_NE(plaintext, decrypted);
}
TEST(SyncNigoriTest, CorruptedCiphertext) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string plaintext("test");
std::string encrypted;
EXPECT_TRUE(nigori->Encrypt(plaintext, &encrypted));
// Corrput the ciphertext by changing one of its bytes.
encrypted[Nigori::kIvSize + 10] =
(encrypted[Nigori::kIvSize + 10] == 'a' ? 'b' : 'a');
std::string decrypted;
EXPECT_FALSE(nigori->Decrypt(encrypted, &decrypted));
EXPECT_NE(plaintext, decrypted);
}
TEST(SyncNigoriTest, ExportImport) {
std::unique_ptr<Nigori> nigori1 = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori1, NotNull());
std::string user_key;
std::string encryption_key;
std::string mac_key;
nigori1->ExportKeys(&user_key, &encryption_key, &mac_key);
std::unique_ptr<Nigori> nigori2 =
Nigori::CreateByImport(user_key, encryption_key, mac_key);
ASSERT_THAT(nigori2, NotNull());
std::string original("test");
std::string plaintext;
std::string ciphertext;
EXPECT_TRUE(nigori1->Encrypt(original, &ciphertext));
EXPECT_TRUE(nigori2->Decrypt(ciphertext, &plaintext));
EXPECT_EQ(original, plaintext);
EXPECT_TRUE(nigori2->Encrypt(original, &ciphertext));
EXPECT_TRUE(nigori1->Decrypt(ciphertext, &plaintext));
EXPECT_EQ(original, plaintext);
std::string permuted1, permuted2;
EXPECT_TRUE(nigori1->Permute(Nigori::Password, original, &permuted1));
EXPECT_TRUE(nigori2->Permute(Nigori::Password, original, &permuted2));
EXPECT_EQ(permuted1, permuted2);
}
TEST(SyncNigoriTest, CreateByDerivationSetsUserKey) {
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori, NotNull());
std::string user_key;
std::string encryption_key;
std::string mac_key;
nigori->ExportKeys(&user_key, &encryption_key, &mac_key);
EXPECT_NE(user_key, "");
}
TEST(SyncNigoriTest, ToleratesEmptyUserKey) {
std::unique_ptr<Nigori> nigori1 = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), "password");
ASSERT_THAT(nigori1, NotNull());
std::string user_key;
std::string encryption_key;
std::string mac_key;
nigori1->ExportKeys(&user_key, &encryption_key, &mac_key);
EXPECT_FALSE(user_key.empty());
EXPECT_FALSE(encryption_key.empty());
EXPECT_FALSE(mac_key.empty());
std::unique_ptr<Nigori> nigori2 =
Nigori::CreateByImport(/*user_key=*/"", encryption_key, mac_key);
ASSERT_THAT(nigori2, NotNull());
user_key = "non-empty-value";
nigori2->ExportKeys(&user_key, &encryption_key, &mac_key);
EXPECT_TRUE(user_key.empty());
EXPECT_FALSE(encryption_key.empty());
EXPECT_FALSE(mac_key.empty());
}
TEST(SyncNigoriTest, CreateByDerivationShouldDeriveCorrectKeyUsingPbkdf2) {
const std::string kPassphrase = "hunter2";
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForPbkdf2(), kPassphrase);
ASSERT_THAT(nigori, NotNull());
std::string user_key;
std::string encryption_key;
std::string mac_key;
nigori->ExportKeys(&user_key, &encryption_key, &mac_key);
// These are reference values obtained by running PBKDF2 with Nigori's
// parameters and the input values given above.
EXPECT_EQ(
"025599e143c4923d77f65b99d97019a3",
base::ToLowerASCII(base::HexEncode(user_key.data(), user_key.size())));
EXPECT_EQ("4596bf346572497d92b2a0e2146d93c1",
base::ToLowerASCII(
base::HexEncode(encryption_key.data(), encryption_key.size())));
EXPECT_EQ(
"2292ad9db96fe590b22a58db50f6f545",
base::ToLowerASCII(base::HexEncode(mac_key.data(), mac_key.size())));
}
TEST(SyncNigoriTest, CreateByDerivationShouldDeriveCorrectKeyUsingScrypt) {
const std::string kSalt = "alpensalz";
const std::string kPassphrase = "hunter2";
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
KeyDerivationParams::CreateForScrypt(kSalt), kPassphrase);
ASSERT_THAT(nigori, NotNull());
std::string user_key;
std::string encryption_key;
std::string mac_key;
nigori->ExportKeys(&user_key, &encryption_key, &mac_key);
// user_key is not used anymore, but is being set for backwards compatibility
// (because legacy clients cannot import a Nigori node without one).
// Therefore, we just initialize it to all zeroes.
EXPECT_EQ(
"00000000000000000000000000000000",
base::ToLowerASCII(base::HexEncode(user_key.data(), user_key.size())));
// These are reference values obtained by running scrypt with Nigori's
// parameters and the input values given above.
EXPECT_EQ("8aa735e0091339a5e51da3b3dd1b328a",
base::ToLowerASCII(
base::HexEncode(encryption_key.data(), encryption_key.size())));
EXPECT_EQ(
"a7e73611968dfd2bca5b3382aed451ba",
base::ToLowerASCII(base::HexEncode(mac_key.data(), mac_key.size())));
}
TEST(SyncNigoriTest, CreateByDerivationShouldReportPbkdf2DurationInHistogram) {
FakeTickClock fake_tick_clock;
base::HistogramTester histogram_tester;
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivationForTesting(
KeyDerivationParams::CreateForPbkdf2(), "Passphrase!", &fake_tick_clock);
ASSERT_THAT(nigori, NotNull());
ASSERT_EQ(2, fake_tick_clock.call_count());
histogram_tester.ExpectUniqueSample(
"Sync.Crypto.NigoriKeyDerivationDuration.Pbkdf2",
/*sample=*/FakeTickClock::kTicksAdvanceAfterEachCall.InMilliseconds(),
/*count=*/1);
}
TEST(SyncNigoriTest, CreateByDerivationShouldReportScryptDurationInHistogram) {
FakeTickClock fake_tick_clock;
base::HistogramTester histogram_tester;
std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivationForTesting(
KeyDerivationParams::CreateForScrypt("somesalt"), "Passphrase!",
&fake_tick_clock);
ASSERT_THAT(nigori, NotNull());
ASSERT_EQ(2, fake_tick_clock.call_count());
histogram_tester.ExpectUniqueSample(
"Sync.Crypto.NigoriKeyDerivationDuration.Scrypt8192",
/*sample=*/FakeTickClock::kTicksAdvanceAfterEachCall.InMilliseconds(),
/*count=*/1);
}
TEST(SyncNigoriTest, GenerateScryptSaltShouldReturnSaltOfCorrectSize) {
EXPECT_EQ(32U, Nigori::GenerateScryptSalt().size());
}
TEST(SyncNigoriTest, GenerateScryptSaltShouldReturnNontrivialSalt) {
std::string salt = Nigori::GenerateScryptSalt();
// Check that there are at least two different bytes in the salt. Guards
// against e.g. a salt of all zeroes.
bool is_nontrivial = false;
for (char c : salt) {
if (c != salt[0]) {
is_nontrivial = true;
break;
}
}
EXPECT_TRUE(is_nontrivial);
}
TEST(SyncNigoriTest, ShouldFailToImportEmpty) {
EXPECT_THAT(Nigori::CreateByImport("", "", ""), IsNull());
}
TEST(SyncNigoriTest, ShouldFailToImportInvalid) {
EXPECT_THAT(Nigori::CreateByImport("foo", "bar", "baz"), IsNull());
}
} // anonymous namespace
} // namespace syncer