buffet: Move privetd sources into buffet
No functional changes, only renaming, fixed include paths and include
guards to avoid resubmit warnings.
BUG=brillo:1161
CQ-DEPEND=CL:276521
TEST=none
Change-Id: Icacff92aef47fdd46542bc96eba3ffbb4df6241a
Reviewed-on: https://chromium-review.googlesource.com/276319
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/privet/security_manager_unittest.cc b/buffet/privet/security_manager_unittest.cc
new file mode 100644
index 0000000..32b7463
--- /dev/null
+++ b/buffet/privet/security_manager_unittest.cc
@@ -0,0 +1,316 @@
+// Copyright 2014 The Chromium OS 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 "buffet/privet/security_manager.h"
+
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/strings/string_utils.h>
+#include <crypto/p224_spake.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/openssl_utils.h"
+
+using testing::Eq;
+using testing::_;
+
+namespace privetd {
+
+namespace {
+
+bool IsBase64Char(char c) {
+ return isalnum(c) || (c == '+') || (c == '/') || (c == '=');
+}
+
+bool IsBase64(const std::string& text) {
+ return !text.empty() &&
+ !std::any_of(text.begin(), text.end(),
+ std::not1(std::ref(IsBase64Char)));
+}
+
+class MockPairingCallbacks {
+ public:
+ MOCK_METHOD3(OnPairingStart,
+ void(const std::string& session_id,
+ PairingType pairing_type,
+ const std::vector<uint8_t>& code));
+ MOCK_METHOD1(OnPairingEnd, void(const std::string& session_id));
+};
+
+base::FilePath GetTempFilePath() {
+ base::FilePath file_path;
+ EXPECT_TRUE(base::CreateTemporaryFile(&file_path));
+ return file_path;
+}
+
+} // namespace
+
+class SecurityManagerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ chromeos::Blob fingerprint;
+ fingerprint.resize(256 / 8);
+ base::RandBytes(fingerprint.data(), fingerprint.size());
+ security_.SetCertificateFingerprint(fingerprint);
+
+ chromeos::KeyValueStore store;
+ store.SetString("embedded_code", "1234");
+ EXPECT_TRUE(store.Save(embedded_code_path_));
+ }
+
+ void TearDown() override { base::DeleteFile(embedded_code_path_, false); }
+
+ protected:
+ void PairAndAuthenticate(std::string* fingerprint, std::string* signature) {
+ std::string session_id;
+ std::string device_commitment_base64;
+
+ EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment_base64, nullptr));
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_FALSE(device_commitment_base64.empty());
+
+ crypto::P224EncryptedKeyExchange spake{
+ crypto::P224EncryptedKeyExchange::kPeerTypeClient, "1234"};
+
+ std::string client_commitment_base64{
+ chromeos::data_encoding::Base64Encode(spake.GetNextMessage())};
+
+ EXPECT_TRUE(security_.ConfirmPairing(session_id, client_commitment_base64,
+ fingerprint, signature, nullptr));
+ EXPECT_TRUE(IsBase64(*fingerprint));
+ EXPECT_TRUE(IsBase64(*signature));
+
+ chromeos::Blob device_commitment;
+ ASSERT_TRUE(chromeos::data_encoding::Base64Decode(device_commitment_base64,
+ &device_commitment));
+ spake.ProcessMessage(
+ chromeos::string_utils::GetBytesAsString(device_commitment));
+
+ chromeos::Blob auth_code{
+ HmacSha256(chromeos::SecureBlob{spake.GetUnverifiedKey()},
+ chromeos::SecureBlob{session_id})};
+
+ std::string auth_code_base64{
+ chromeos::data_encoding::Base64Encode(auth_code)};
+
+ EXPECT_TRUE(security_.IsValidPairingCode(auth_code_base64));
+ }
+
+ const base::Time time_ = base::Time::FromTimeT(1410000000);
+ base::MessageLoop message_loop_;
+ base::FilePath embedded_code_path_{GetTempFilePath()};
+ SecurityManager security_{{PairingType::kEmbeddedCode}, embedded_code_path_};
+};
+
+TEST_F(SecurityManagerTest, IsBase64) {
+ EXPECT_TRUE(IsBase64(
+ security_.CreateAccessToken(UserInfo{AuthScope::kUser, 7}, time_)));
+}
+
+TEST_F(SecurityManagerTest, CreateSameToken) {
+ EXPECT_EQ(
+ security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_),
+ security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentScope) {
+ EXPECT_NE(
+ security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}, time_),
+ security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentUser) {
+ EXPECT_NE(
+ security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_),
+ security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentTime) {
+ EXPECT_NE(
+ security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}, time_),
+ security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567},
+ base::Time::FromTimeT(1400000000)));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentInstance) {
+ EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_),
+ SecurityManager({}, base::FilePath{})
+ .CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_));
+}
+
+TEST_F(SecurityManagerTest, ParseAccessToken) {
+ // Multiple attempts with random secrets.
+ for (size_t i = 0; i < 1000; ++i) {
+ SecurityManager security{{}, base::FilePath{}};
+
+ std::string token =
+ security.CreateAccessToken(UserInfo{AuthScope::kUser, 5}, time_);
+ base::Time time2;
+ EXPECT_EQ(AuthScope::kUser,
+ security.ParseAccessToken(token, &time2).scope());
+ EXPECT_EQ(5u, security.ParseAccessToken(token, &time2).user_id());
+ // Token timestamp resolution is one second.
+ EXPECT_GE(1, std::abs((time_ - time2).InSeconds()));
+ }
+}
+
+TEST_F(SecurityManagerTest, PairingNoSession) {
+ std::string fingerprint;
+ std::string signature;
+ chromeos::ErrorPtr error;
+ ASSERT_FALSE(
+ security_.ConfirmPairing("123", "345", &fingerprint, &signature, &error));
+ EXPECT_EQ("unknownSession", error->GetCode());
+}
+
+TEST_F(SecurityManagerTest, Pairing) {
+ std::vector<std::pair<std::string, std::string> > fingerprints(2);
+ for (auto& it : fingerprints) {
+ PairAndAuthenticate(&it.first, &it.second);
+ }
+
+ // Same certificate.
+ EXPECT_EQ(fingerprints.front().first, fingerprints.back().first);
+
+ // Signed with different secret.
+ EXPECT_NE(fingerprints.front().second, fingerprints.back().second);
+}
+
+TEST_F(SecurityManagerTest, NotifiesListenersOfSessionStartAndEnd) {
+ testing::StrictMock<MockPairingCallbacks> callbacks;
+ security_.RegisterPairingListeners(
+ base::Bind(&MockPairingCallbacks::OnPairingStart,
+ base::Unretained(&callbacks)),
+ base::Bind(&MockPairingCallbacks::OnPairingEnd,
+ base::Unretained(&callbacks)));
+ for (auto commitment_suffix :
+ std::vector<std::string>{"", "invalid_commitment"}) {
+ // StartPairing should notify us that a new session has begun.
+ std::string session_id;
+ std::string device_commitment;
+ EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
+ EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment, nullptr));
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_FALSE(device_commitment.empty());
+ testing::Mock::VerifyAndClearExpectations(&callbacks);
+
+ // ConfirmPairing should notify us that the session has ended.
+ EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
+ crypto::P224EncryptedKeyExchange spake{
+ crypto::P224EncryptedKeyExchange::kPeerTypeServer, "1234"};
+ std::string client_commitment =
+ chromeos::data_encoding::Base64Encode(spake.GetNextMessage());
+ std::string fingerprint, signature;
+ // Regardless of whether the commitment is valid or not, we should get a
+ // callback indicating that the pairing session is gone.
+ security_.ConfirmPairing(session_id, client_commitment + commitment_suffix,
+ &fingerprint, &signature, nullptr);
+ testing::Mock::VerifyAndClearExpectations(&callbacks);
+ }
+}
+
+TEST_F(SecurityManagerTest, CancelPairing) {
+ testing::StrictMock<MockPairingCallbacks> callbacks;
+ security_.RegisterPairingListeners(
+ base::Bind(&MockPairingCallbacks::OnPairingStart,
+ base::Unretained(&callbacks)),
+ base::Bind(&MockPairingCallbacks::OnPairingEnd,
+ base::Unretained(&callbacks)));
+ std::string session_id;
+ std::string device_commitment;
+ EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
+ EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment, nullptr));
+ EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
+ EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
+}
+
+TEST_F(SecurityManagerTest, ThrottlePairing) {
+ auto pair = [this]() {
+ std::string session_id;
+ std::string device_commitment;
+ chromeos::ErrorPtr error;
+ bool result = security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment, &error);
+ EXPECT_TRUE(result || error->GetCode() == "deviceBusy");
+ return result;
+ };
+
+ EXPECT_TRUE(pair());
+ EXPECT_TRUE(pair());
+ EXPECT_TRUE(pair());
+ EXPECT_FALSE(pair());
+ EXPECT_GT(security_.block_pairing_until_, base::Time::Now());
+ EXPECT_LE(security_.block_pairing_until_,
+ base::Time::Now() + base::TimeDelta::FromMinutes(15));
+
+ // Wait timeout.
+ security_.block_pairing_until_ =
+ base::Time::Now() - base::TimeDelta::FromMinutes(1);
+
+ // Allow exactly one attempt.
+ EXPECT_TRUE(pair());
+ EXPECT_FALSE(pair());
+
+ // Wait timeout.
+ security_.block_pairing_until_ =
+ base::Time::Now() - base::TimeDelta::FromMinutes(1);
+
+ // Completely unblock by successfully pairing.
+ std::string fingerprint;
+ std::string signature;
+ PairAndAuthenticate(&fingerprint, &signature);
+
+ // Now we have 3 attempts again.
+ EXPECT_TRUE(pair());
+ EXPECT_TRUE(pair());
+ EXPECT_TRUE(pair());
+ EXPECT_FALSE(pair());
+}
+
+TEST_F(SecurityManagerTest, DontBlockForCanceledSessions) {
+ for (int i = 0; i < 20; ++i) {
+ std::string session_id;
+ std::string device_commitment;
+ EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment, nullptr));
+ EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
+ }
+}
+
+TEST_F(SecurityManagerTest, EmbeddedCodeNotReady) {
+ std::string session_id;
+ std::string device_commitment;
+ base::DeleteFile(embedded_code_path_, false);
+ chromeos::ErrorPtr error;
+ ASSERT_FALSE(security_.StartPairing(PairingType::kEmbeddedCode,
+ CryptoType::kSpake_p224, &session_id,
+ &device_commitment, &error));
+ EXPECT_EQ("deviceBusy", error->GetCode());
+}
+
+} // namespace privetd