From 7ecdf959f10b62f192be867c280a7885626d6b85 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Fri, 15 Jan 2016 12:19:17 -0800 Subject: Add |name| into LoadSettings/SaveSettings Libweave needs to store more than one config file. BUG:25776798 Change-Id: Ia951c6ef4f06a67318d3ccdb5cf7d8510cb3de58 Reviewed-on: https://weave-review.googlesource.com/2198 Reviewed-by: Alex Vakulenko --- examples/provider/file_config_store.cc | 27 ++++++++++++------ examples/provider/file_config_store.h | 7 +++-- include/weave/provider/config_store.h | 19 +++++++++---- include/weave/provider/test/mock_config_store.h | 6 ++-- src/config.cc | 13 ++++++--- src/config_unittest.cc | 37 +++++++++++++++++-------- src/weave_unittest.cc | 3 +- 7 files changed, 78 insertions(+), 34 deletions(-) diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc index 6faa242..af887a7 100644 --- a/examples/provider/file_config_store.cc +++ b/examples/provider/file_config_store.cc @@ -19,9 +19,15 @@ const char kSettingsDir[] = "/var/lib/weave/"; FileConfigStore::FileConfigStore(bool disable_security, const std::string& model_id) - : disable_security_{disable_security}, - model_id_{model_id}, - settings_path_{"/var/lib/weave/weave_settings_" + model_id + ".json"} {} + : disable_security_{disable_security}, model_id_{model_id} {} + +std::string FileConfigStore::GetPath(const std::string& name) const { + std::string path{kSettingsDir}; + path += path + "weave_settings_" + model_id_; + if (!name.empty()) + path += "_" + name; + return path + ".json"; +} bool FileConfigStore::LoadDefaults(Settings* settings) { char host_name[HOST_NAME_MAX] = {}; @@ -55,16 +61,21 @@ bool FileConfigStore::LoadDefaults(Settings* settings) { } std::string FileConfigStore::LoadSettings() { - LOG(INFO) << "Loading settings from " << settings_path_; - std::ifstream str(settings_path_); + return LoadSettings(""); +} + +std::string FileConfigStore::LoadSettings(const std::string& name) { + LOG(INFO) << "Loading settings from " << GetPath(name); + std::ifstream str(GetPath(name)); return std::string(std::istreambuf_iterator(str), std::istreambuf_iterator()); } -void FileConfigStore::SaveSettings(const std::string& settings) { +void FileConfigStore::SaveSettings(const std::string& name, + const std::string& settings) { CHECK(mkdir(kSettingsDir, S_IRWXU) == 0 || errno == EEXIST); - LOG(INFO) << "Saving settings to " << settings_path_; - std::ofstream str(settings_path_); + LOG(INFO) << "Saving settings to " << GetPath(name); + std::ofstream str(GetPath(name)); str << settings; } diff --git a/examples/provider/file_config_store.h b/examples/provider/file_config_store.h index 578f940..214194e 100644 --- a/examples/provider/file_config_store.h +++ b/examples/provider/file_config_store.h @@ -19,13 +19,16 @@ class FileConfigStore : public provider::ConfigStore { FileConfigStore(bool disable_security, const std::string& model_id); bool LoadDefaults(Settings* settings) override; + std::string LoadSettings(const std::string& name) override; + void SaveSettings(const std::string& name, + const std::string& settings) override; + std::string LoadSettings() override; - void SaveSettings(const std::string& settings) override; private: + std::string GetPath(const std::string& name) const; const bool disable_security_; const std::string model_id_; - const std::string settings_path_; }; } // namespace examples diff --git a/include/weave/provider/config_store.h b/include/weave/provider/config_store.h index 1b7988f..991d750 100644 --- a/include/weave/provider/config_store.h +++ b/include/weave/provider/config_store.h @@ -36,8 +36,8 @@ namespace provider { // Implementation of LoadSettings() method should load previously // stored settings from the persistent storage (file, flash, etc). // For example: -// std::string FileConfigStore::LoadSettings() { -// std::ifstream str("/var/lib/weave/weave_settings.json"); +// std::string FileConfigStore::LoadSettings(const std::string& name) { +// std::ifstream str("/var/lib/weave/weave_" + name + ".json"); // return std::string(std::istreambuf_iterator(str), // std::istreambuf_iterator()); // } @@ -47,8 +47,9 @@ namespace provider { // Implementation of SaveSettings(...) method should store data in the // persistent storage (file, flash, etc). // For example: -// void FileConfigStore::SaveSettings(const std::string& settings) { -// std::ofstream str(kSettingsPath); +// void FileConfigStore::SaveSettings(const std::string& name, +// const std::string& settings) { +// std::ofstream str("/var/lib/weave/weave_" + name + ".json"); // str << settings; // } // It is highly recommended to protected data using encryption with @@ -67,12 +68,18 @@ class ConfigStore { // Returns settings saved by SaveSettings during last run of libweave. // Implementation should return data as-is without parsing or modifications. - virtual std::string LoadSettings() = 0; + // |name| is the name of settings blob. Could be used as filename. + virtual std::string LoadSettings(const std::string& name) = 0; // Saves settings. Implementation should save data as-is without parsing or // modifications. Data stored in settings can be sensitive, so it's highly // recommended to protect data, e.g. using encryption. - virtual void SaveSettings(const std::string& settings) = 0; + // |name| is the name of settings blob. Could be used as filename. + virtual void SaveSettings(const std::string& name, + const std::string& settings) = 0; + + // Deprecated: only for migration of old configs to version with |name|. + virtual std::string LoadSettings() = 0; protected: virtual ~ConfigStore() {} diff --git a/include/weave/provider/test/mock_config_store.h b/include/weave/provider/test/mock_config_store.h index 3873251..cdae693 100644 --- a/include/weave/provider/test/mock_config_store.h +++ b/include/weave/provider/test/mock_config_store.h @@ -39,11 +39,13 @@ class MockConfigStore : public ConfigStore { "version": 1, "device_id": "TEST_DEVICE_ID" })")); - EXPECT_CALL(*this, SaveSettings(_)).WillRepeatedly(Return()); + EXPECT_CALL(*this, LoadSettings("config")).WillRepeatedly(Return("")); + EXPECT_CALL(*this, SaveSettings("config", _)).WillRepeatedly(Return()); } MOCK_METHOD1(LoadDefaults, bool(Settings*)); + MOCK_METHOD1(LoadSettings, std::string(const std::string&)); + MOCK_METHOD2(SaveSettings, void(const std::string&, const std::string&)); MOCK_METHOD0(LoadSettings, std::string()); - MOCK_METHOD1(SaveSettings, void(const std::string&)); }; } // namespace test diff --git a/src/config.cc b/src/config.cc index 37e907c..510a940 100644 --- a/src/config.cc +++ b/src/config.cc @@ -21,6 +21,8 @@ namespace weave { +const char kConfigName[] = "config"; + namespace config_keys { const char kVersion[] = "version"; @@ -140,9 +142,12 @@ void Config::Load() { void Config::Transaction::LoadState() { if (!config_->config_store_) return; - std::string json_string = config_->config_store_->LoadSettings(); - if (json_string.empty()) - return; + std::string json_string = config_->config_store_->LoadSettings(kConfigName); + if (json_string.empty()) { + json_string = config_->config_store_->LoadSettings(); + if (json_string.empty()) + return; + } auto value = base::JSONReader::Read(json_string); base::DictionaryValue* dict = nullptr; @@ -267,7 +272,7 @@ void Config::Save() { base::JSONWriter::WriteWithOptions( dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string); - config_store_->SaveSettings(json_string); + config_store_->SaveSettings(kConfigName, json_string); } Config::Transaction::~Transaction() { diff --git a/src/config_unittest.cc b/src/config_unittest.cc index 0367516..4517428 100644 --- a/src/config_unittest.cc +++ b/src/config_unittest.cc @@ -17,18 +17,20 @@ using testing::_; using testing::Invoke; using testing::Return; +using testing::WithArgs; namespace weave { +const char kConfigName[] = "config"; + class ConfigTest : public ::testing::Test { protected: void SetUp() override { - EXPECT_CALL(*this, OnConfigChanged(_)) - .Times(1); // Called from AddOnChangedCallback Reload(); } void Reload() { + EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); config_.reset(new Config{&config_store_}); config_->AddOnChangedCallback( base::Bind(&ConfigTest::OnConfigChanged, base::Unretained(this))); @@ -86,31 +88,45 @@ TEST_F(ConfigTest, Defaults) { } TEST_F(ConfigTest, LoadStateV0) { - EXPECT_CALL(config_store_, LoadSettings()) + EXPECT_CALL(config_store_, LoadSettings(kConfigName)) .WillOnce(Return(R"({ "device_id": "state_device_id" })")); - EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); Reload(); EXPECT_EQ("state_device_id", GetSettings().cloud_id); EXPECT_FALSE(GetSettings().device_id.empty()); EXPECT_NE(GetSettings().cloud_id, GetSettings().device_id); - EXPECT_CALL(config_store_, LoadSettings()) + EXPECT_CALL(config_store_, LoadSettings(kConfigName)) .WillOnce(Return(R"({ "device_id": "state_device_id", "cloud_id": "state_cloud_id" })")); - EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); Reload(); EXPECT_EQ("state_cloud_id", GetSettings().cloud_id); EXPECT_EQ("state_device_id", GetSettings().device_id); } +TEST_F(ConfigTest, LoadStateUnnamed) { + EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return("")); + + EXPECT_CALL(config_store_, LoadSettings()).Times(1); + + Reload(); +} + +TEST_F(ConfigTest, LoadStateNamed) { + EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return("{}")); + + EXPECT_CALL(config_store_, LoadSettings()).Times(0); + + Reload(); +} + TEST_F(ConfigTest, LoadState) { auto state = R"({ "version": 1, @@ -133,9 +149,8 @@ TEST_F(ConfigTest, LoadState) { "secret": "c3RhdGVfc2VjcmV0", "service_url": "state_service_url" })"; - EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(state)); + EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return(state)); - EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); Reload(); EXPECT_EQ("state_client_id", GetSettings().client_id); @@ -243,8 +258,8 @@ TEST_F(ConfigTest, Setters) { EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); - EXPECT_CALL(config_store_, SaveSettings(_)) - .WillOnce(Invoke([](const std::string& json) { + EXPECT_CALL(config_store_, SaveSettings(kConfigName, _)) + .WillOnce(WithArgs<1>(Invoke([](const std::string& json) { auto expected = R"({ 'version': 1, 'api_key': 'set_api_key', @@ -267,7 +282,7 @@ TEST_F(ConfigTest, Setters) { 'service_url': 'set_service_url' })"; EXPECT_JSON_EQ(expected, *test::CreateValue(json)); - })); + }))); change.Commit(); } diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc index b106aab..1ba116e 100644 --- a/src/weave_unittest.cc +++ b/src/weave_unittest.cc @@ -205,7 +205,8 @@ class WeaveTest : public ::testing::Test { } void InitConfigStore() { - EXPECT_CALL(config_store_, SaveSettings("")).WillRepeatedly(Return()); + EXPECT_CALL(config_store_, SaveSettings("config", _)) + .WillRepeatedly(Return()); } void InitNetwork() { -- cgit v1.2.3 From 42e508f2559e019d2fcc8f88adfd184b7a6bc3a4 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Fri, 15 Jan 2016 14:48:54 -0800 Subject: Add write callback into SaveSettings function Saving critical settings needs confirmation. When command alters device config, it should be set "Done" only after settings are actually saved. BUG:25776798 Change-Id: Id8f52a2922aa0e58f66eb3e257197a47e21ef3a2 Reviewed-on: https://weave-review.googlesource.com/2199 Reviewed-by: Alex Vakulenko --- examples/daemon/common/daemon.h | 11 ++++++----- examples/provider/file_config_store.cc | 14 +++++++++++--- examples/provider/file_config_store.h | 9 +++++++-- include/weave/provider/config_store.h | 11 +++++++++-- include/weave/provider/test/mock_config_store.h | 12 ++++++++++-- src/config.cc | 5 ++++- src/config_unittest.cc | 12 +++++++----- src/weave_unittest.cc | 9 --------- 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/examples/daemon/common/daemon.h b/examples/daemon/common/daemon.h index 4cccff3..6dc021d 100644 --- a/examples/daemon/common/daemon.h +++ b/examples/daemon/common/daemon.h @@ -69,10 +69,11 @@ class Daemon { }; Daemon(const Options& opts) - : config_store_{new weave::examples::FileConfigStore( - opts.disable_security_, - opts.model_id_)}, - task_runner_{new weave::examples::EventTaskRunner}, + : task_runner_{new weave::examples::EventTaskRunner}, + config_store_{ + new weave::examples::FileConfigStore(opts.disable_security_, + opts.model_id_, + task_runner_.get())}, http_client_{new weave::examples::CurlHttpClient(task_runner_.get())}, network_{new weave::examples::EventNetworkImpl(task_runner_.get())}, bluetooth_{new weave::examples::BluetoothImpl} { @@ -114,8 +115,8 @@ class Daemon { LOG(INFO) << "Device registered: " << device->GetSettings().cloud_id; } - std::unique_ptr config_store_; std::unique_ptr task_runner_; + std::unique_ptr config_store_; std::unique_ptr http_client_; std::unique_ptr network_; std::unique_ptr bluetooth_; diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc index af887a7..31efaa7 100644 --- a/examples/provider/file_config_store.cc +++ b/examples/provider/file_config_store.cc @@ -12,14 +12,19 @@ #include #include +#include + namespace weave { namespace examples { const char kSettingsDir[] = "/var/lib/weave/"; FileConfigStore::FileConfigStore(bool disable_security, - const std::string& model_id) - : disable_security_{disable_security}, model_id_{model_id} {} + const std::string& model_id, + provider::TaskRunner* task_runner) + : disable_security_{disable_security}, + model_id_{model_id}, + task_runner_{task_runner} {} std::string FileConfigStore::GetPath(const std::string& name) const { std::string path{kSettingsDir}; @@ -72,11 +77,14 @@ std::string FileConfigStore::LoadSettings(const std::string& name) { } void FileConfigStore::SaveSettings(const std::string& name, - const std::string& settings) { + const std::string& settings, + const DoneCallback& callback) { CHECK(mkdir(kSettingsDir, S_IRWXU) == 0 || errno == EEXIST); LOG(INFO) << "Saving settings to " << GetPath(name); std::ofstream str(GetPath(name)); str << settings; + if (!callback.is_null()) + task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {}); } } // namespace examples diff --git a/examples/provider/file_config_store.h b/examples/provider/file_config_store.h index 214194e..e7398d1 100644 --- a/examples/provider/file_config_store.h +++ b/examples/provider/file_config_store.h @@ -10,18 +10,22 @@ #include #include +#include namespace weave { namespace examples { class FileConfigStore : public provider::ConfigStore { public: - FileConfigStore(bool disable_security, const std::string& model_id); + FileConfigStore(bool disable_security, + const std::string& model_id, + provider::TaskRunner* task_runner); bool LoadDefaults(Settings* settings) override; std::string LoadSettings(const std::string& name) override; void SaveSettings(const std::string& name, - const std::string& settings) override; + const std::string& settings, + const DoneCallback& callback) override; std::string LoadSettings() override; @@ -29,6 +33,7 @@ class FileConfigStore : public provider::ConfigStore { std::string GetPath(const std::string& name) const; const bool disable_security_; const std::string model_id_; + provider::TaskRunner* task_runner_{nullptr}; }; } // namespace examples diff --git a/include/weave/provider/config_store.h b/include/weave/provider/config_store.h index 991d750..128eccc 100644 --- a/include/weave/provider/config_store.h +++ b/include/weave/provider/config_store.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace weave { @@ -48,9 +49,13 @@ namespace provider { // persistent storage (file, flash, etc). // For example: // void FileConfigStore::SaveSettings(const std::string& name, -// const std::string& settings) { +// const std::string& settings, +// const DoneCallback& callback) { // std::ofstream str("/var/lib/weave/weave_" + name + ".json"); // str << settings; +// if (!callback.is_null()) +// task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), +// {}); // } // It is highly recommended to protected data using encryption with // hardware backed key. @@ -75,8 +80,10 @@ class ConfigStore { // modifications. Data stored in settings can be sensitive, so it's highly // recommended to protect data, e.g. using encryption. // |name| is the name of settings blob. Could be used as filename. + // Implementation must call or post callback virtual void SaveSettings(const std::string& name, - const std::string& settings) = 0; + const std::string& settings, + const DoneCallback& callback) = 0; // Deprecated: only for migration of old configs to version with |name|. virtual std::string LoadSettings() = 0; diff --git a/include/weave/provider/test/mock_config_store.h b/include/weave/provider/test/mock_config_store.h index cdae693..e6411d6 100644 --- a/include/weave/provider/test/mock_config_store.h +++ b/include/weave/provider/test/mock_config_store.h @@ -40,11 +40,19 @@ class MockConfigStore : public ConfigStore { "device_id": "TEST_DEVICE_ID" })")); EXPECT_CALL(*this, LoadSettings("config")).WillRepeatedly(Return("")); - EXPECT_CALL(*this, SaveSettings("config", _)).WillRepeatedly(Return()); + EXPECT_CALL(*this, SaveSettings("config", _, _)) + .WillRepeatedly(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + if (!callback.is_null()) + callback.Run(nullptr); + }))); } MOCK_METHOD1(LoadDefaults, bool(Settings*)); MOCK_METHOD1(LoadSettings, std::string(const std::string&)); - MOCK_METHOD2(SaveSettings, void(const std::string&, const std::string&)); + MOCK_METHOD3(SaveSettings, + void(const std::string&, + const std::string&, + const DoneCallback&)); MOCK_METHOD0(LoadSettings, std::string()); }; diff --git a/src/config.cc b/src/config.cc index 510a940..4c6b140 100644 --- a/src/config.cc +++ b/src/config.cc @@ -18,6 +18,7 @@ #include "src/data_encoding.h" #include "src/privet/privet_types.h" #include "src/string_utils.h" +#include "src/bind_lambda.h" namespace weave { @@ -272,7 +273,9 @@ void Config::Save() { base::JSONWriter::WriteWithOptions( dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string); - config_store_->SaveSettings(kConfigName, json_string); + config_store_->SaveSettings( + kConfigName, json_string, + base::Bind([](ErrorPtr error) { CHECK(!error); })); } Config::Transaction::~Transaction() { diff --git a/src/config_unittest.cc b/src/config_unittest.cc index 4517428..fbb558a 100644 --- a/src/config_unittest.cc +++ b/src/config_unittest.cc @@ -258,9 +258,10 @@ TEST_F(ConfigTest, Setters) { EXPECT_CALL(*this, OnConfigChanged(_)).Times(1); - EXPECT_CALL(config_store_, SaveSettings(kConfigName, _)) - .WillOnce(WithArgs<1>(Invoke([](const std::string& json) { - auto expected = R"({ + EXPECT_CALL(config_store_, SaveSettings(kConfigName, _, _)) + .WillOnce(WithArgs<1, 2>( + Invoke([](const std::string& json, const DoneCallback& callback) { + auto expected = R"({ 'version': 1, 'api_key': 'set_api_key', 'client_id': 'set_client_id', @@ -281,8 +282,9 @@ TEST_F(ConfigTest, Setters) { 'secret': 'AQIDBAU=', 'service_url': 'set_service_url' })"; - EXPECT_JSON_EQ(expected, *test::CreateValue(json)); - }))); + EXPECT_JSON_EQ(expected, *test::CreateValue(json)); + callback.Run(nullptr); + }))); change.Commit(); } diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc index 1ba116e..8df63ae 100644 --- a/src/weave_unittest.cc +++ b/src/weave_unittest.cc @@ -204,11 +204,6 @@ class WeaveTest : public ::testing::Test { }))); } - void InitConfigStore() { - EXPECT_CALL(config_store_, SaveSettings("config", _)) - .WillRepeatedly(Return()); - } - void InitNetwork() { EXPECT_CALL(network_, AddConnectionChangedCallback(_)) .WillRepeatedly(Invoke( @@ -268,7 +263,6 @@ class WeaveTest : public ::testing::Test { } void InitDefaultExpectations() { - InitConfigStore(); InitNetwork(); EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) .WillOnce(Return()); @@ -361,13 +355,11 @@ TEST_F(WeaveTest, Mocks) { } TEST_F(WeaveTest, StartMinimal) { - InitConfigStore(); device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_, &network_, nullptr, nullptr, &wifi_, nullptr); } TEST_F(WeaveTest, StartNoWifi) { - InitConfigStore(); InitNetwork(); InitHttpServer(); InitDnsSd(); @@ -452,7 +444,6 @@ class WeaveWiFiSetupTest : public WeaveTest { void SetUp() override { WeaveTest::SetUp(); - InitConfigStore(); InitHttpServer(); InitNetwork(); InitDnsSd(); -- cgit v1.2.3 From f533677d489d16074485868aaaa74baad47135d1 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Tue, 19 Jan 2016 16:43:56 -0800 Subject: Implemented _accessControlBlackList trait BUG:25768272 BUG:25777665 Change-Id: Id32475816dbd9ec24b020f4f2a68274a978729c8 Reviewed-on: https://weave-review.googlesource.com/2209 Reviewed-by: Vitaly Buka --- file_lists.mk | 2 + src/access_api_handler.cc | 233 ++++++++++++++++++++++++++++++++++ src/access_api_handler.h | 47 +++++++ src/access_api_handler_unittest.cc | 249 +++++++++++++++++++++++++++++++++++++ src/access_black_list_manager.h | 37 ++++++ src/base_api_handler.h | 2 +- 6 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 src/access_api_handler.cc create mode 100644 src/access_api_handler.h create mode 100644 src/access_api_handler_unittest.cc create mode 100644 src/access_black_list_manager.h diff --git a/file_lists.mk b/file_lists.mk index 1e78935..e79372e 100644 --- a/file_lists.mk +++ b/file_lists.mk @@ -3,6 +3,7 @@ # found in the LICENSE file. WEAVE_SRC_FILES := \ + src/access_api_handler.cc \ src/backoff_entry.cc \ src/base_api_handler.cc \ src/commands/cloud_command_proxy.cc \ @@ -48,6 +49,7 @@ WEAVE_TEST_SRC_FILES := \ src/test/unittest_utils.cc WEAVE_UNITTEST_SRC_FILES := \ + src/access_api_handler_unittest.cc \ src/backoff_entry_unittest.cc \ src/base_api_handler_unittest.cc \ src/commands/cloud_command_proxy_unittest.cc \ diff --git a/src/access_api_handler.cc b/src/access_api_handler.cc new file mode 100644 index 0000000..e03a548 --- /dev/null +++ b/src/access_api_handler.cc @@ -0,0 +1,233 @@ +// Copyright 2016 The Weave 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 "src/access_api_handler.h" + +#include +#include + +#include "src/access_black_list_manager.h" +#include "src/commands/schema_constants.h" +#include "src/data_encoding.h" +#include "src/json_error_codes.h" + +namespace weave { + +namespace { + +const char kComponent[] = "accessControl"; +const char kTrait[] = "_accessControlBlackList"; +const char kStateSize[] = "_accessControlBlackList.size"; +const char kStateCapacity[] = "_accessControlBlackList.capacity"; +const char kUserId[] = "userId"; +const char kApplicationId[] = "applicationId"; +const char kExpirationTimeout[] = "expirationTimeoutSec"; +const char kBlackList[] = "blackList"; + +bool GetIds(const base::DictionaryValue& parameters, + std::vector* user_id_decoded, + std::vector* app_id_decoded, + ErrorPtr* error) { + std::string user_id; + parameters.GetString(kUserId, &user_id); + if (!Base64Decode(user_id, user_id_decoded)) { + Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, + errors::commands::kInvalidPropValue, + "Invalid user id '%s'", user_id.c_str()); + return false; + } + + std::string app_id; + parameters.GetString(kApplicationId, &app_id); + if (!Base64Decode(app_id, app_id_decoded)) { + Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, + errors::commands::kInvalidPropValue, + "Invalid app id '%s'", user_id.c_str()); + return false; + } + + return true; +} + +} // namespace + +AccessApiHandler::AccessApiHandler(Device* device, + AccessBlackListManager* manager) + : device_{device}, manager_{manager} { + device_->AddTraitDefinitionsFromJson(R"({ + "_accessControlBlackList": { + "commands": { + "block": { + "minimalRole": "owner", + "parameters": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + }, + "expirationTimeoutSec": { + "type": "integer" + } + } + }, + "unblock": { + "minimalRole": "owner", + "parameters": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + } + } + }, + "list": { + "minimalRole": "owner", + "parameters": {}, + "results": { + "blackList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + }, + "state": { + "size": { + "type": "integer", + "isRequired": true + }, + "capacity": { + "type": "integer", + "isRequired": true + } + } + } + })"); + CHECK(device_->AddComponent(kComponent, {kTrait}, nullptr)); + UpdateState(); + + device_->AddCommandHandler( + kComponent, "_accessControlBlackList.block", + base::Bind(&AccessApiHandler::Block, weak_ptr_factory_.GetWeakPtr())); + device_->AddCommandHandler( + kComponent, "_accessControlBlackList.unblock", + base::Bind(&AccessApiHandler::Unblock, weak_ptr_factory_.GetWeakPtr())); + device_->AddCommandHandler( + kComponent, "_accessControlBlackList.list", + base::Bind(&AccessApiHandler::List, weak_ptr_factory_.GetWeakPtr())); +} + +void AccessApiHandler::Block(const std::weak_ptr& cmd) { + auto command = cmd.lock(); + if (!command) + return; + + CHECK(command->GetState() == Command::State::kQueued) + << EnumToString(command->GetState()); + command->SetProgress(base::DictionaryValue{}, nullptr); + + const auto& parameters = command->GetParameters(); + std::vector user_id; + std::vector app_id; + ErrorPtr error; + if (!GetIds(parameters, &user_id, &app_id, &error)) { + command->Abort(error.get(), nullptr); + return; + } + + int timeout_sec = 0; + parameters.GetInteger(kExpirationTimeout, &timeout_sec); + + base::Time expiration = + base::Time::Now() + base::TimeDelta::FromSeconds(timeout_sec); + + manager_->Block(user_id, app_id, expiration, + base::Bind(&AccessApiHandler::OnCommandDone, + weak_ptr_factory_.GetWeakPtr(), cmd)); +} + +void AccessApiHandler::Unblock(const std::weak_ptr& cmd) { + auto command = cmd.lock(); + if (!command) + return; + + CHECK(command->GetState() == Command::State::kQueued) + << EnumToString(command->GetState()); + command->SetProgress(base::DictionaryValue{}, nullptr); + + const auto& parameters = command->GetParameters(); + std::vector user_id; + std::vector app_id; + ErrorPtr error; + if (!GetIds(parameters, &user_id, &app_id, &error)) { + command->Abort(error.get(), nullptr); + return; + } + + if (!manager_->Unblock(user_id, app_id, &error)) { + command->Abort(error.get(), nullptr); + return; + } + + UpdateState(); + command->Complete({}, nullptr); +} + +void AccessApiHandler::List(const std::weak_ptr& cmd) { + auto command = cmd.lock(); + if (!command) + return; + + CHECK(command->GetState() == Command::State::kQueued) + << EnumToString(command->GetState()); + command->SetProgress(base::DictionaryValue{}, nullptr); + + std::unique_ptr entries{new base::ListValue}; + for (const auto& e : manager_->GetEntries()) { + std::unique_ptr entry{new base::DictionaryValue}; + entry->SetString(kUserId, Base64Encode(e.user_id)); + entry->SetString(kApplicationId, Base64Encode(e.app_id)); + entries->Append(entry.release()); + } + + base::DictionaryValue result; + result.Set(kBlackList, entries.release()); + + command->Complete(result, nullptr); +} + +void AccessApiHandler::OnCommandDone(const std::weak_ptr& cmd, + ErrorPtr error) { + auto command = cmd.lock(); + if (!command) + return; + UpdateState(); + if (error) { + command->Abort(error.get(), nullptr); + return; + } + command->Complete({}, nullptr); +} + +void AccessApiHandler::UpdateState() { + base::DictionaryValue state; + state.SetInteger(kStateSize, manager_->GetSize()); + state.SetInteger(kStateCapacity, manager_->GetCapacity()); + device_->SetStateProperties(kComponent, state, nullptr); +} + +} // namespace weave diff --git a/src/access_api_handler.h b/src/access_api_handler.h new file mode 100644 index 0000000..821ce02 --- /dev/null +++ b/src/access_api_handler.h @@ -0,0 +1,47 @@ +// Copyright 2016 The Weave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBWEAVE_SRC_ACCESS_API_HANDLER_H_ +#define LIBWEAVE_SRC_ACCESS_API_HANDLER_H_ + +#include + +#include +#include + +namespace weave { + +class AccessBlackListManager; +class Command; +class Device; + +// Handles commands for 'accessControlBlackList' trait. +// Objects of the class subscribe for notification from CommandManager and +// execute incoming commands. +// Handled commands: +// accessControlBlackList.block +// accessControlBlackList.unblock +// accessControlBlackList.list +class AccessApiHandler final { + public: + AccessApiHandler(Device* device, AccessBlackListManager* manager); + + private: + void Block(const std::weak_ptr& command); + void Unblock(const std::weak_ptr& command); + void List(const std::weak_ptr& command); + void UpdateState(); + + void OnCommandDone(const std::weak_ptr& command, ErrorPtr error); + + Device* device_{nullptr}; + AccessBlackListManager* manager_{nullptr}; + + base::WeakPtrFactory weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(AccessApiHandler); +}; + +} // namespace weave + +#endif // LIBWEAVE_SRC_ACCESS_API_HANDLER_H_ diff --git a/src/access_api_handler_unittest.cc b/src/access_api_handler_unittest.cc new file mode 100644 index 0000000..3858722 --- /dev/null +++ b/src/access_api_handler_unittest.cc @@ -0,0 +1,249 @@ +// Copyright 2016 The Weave 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 "src/access_api_handler.h" + +#include +#include +#include + +#include "src/component_manager_impl.h" +#include "src/access_black_list_manager.h" +#include "src/data_encoding.h" + +using testing::_; +using testing::AnyOf; +using testing::Invoke; +using testing::Return; +using testing::StrictMock; +using testing::WithArgs; + +namespace weave { + +class MockAccessBlackListManager : public AccessBlackListManager { + public: + MOCK_METHOD4(Block, + void(const std::vector&, + const std::vector&, + const base::Time&, + const DoneCallback&)); + MOCK_METHOD3(Unblock, + bool(const std::vector&, + const std::vector&, + ErrorPtr*)); + MOCK_CONST_METHOD0(GetEntries, std::vector()); + MOCK_CONST_METHOD0(GetSize, size_t()); + MOCK_CONST_METHOD0(GetCapacity, size_t()); +}; + +class AccessApiHandlerTest : public ::testing::Test { + protected: + void SetUp() override { + EXPECT_CALL(device_, AddTraitDefinitionsFromJson(_)) + .WillRepeatedly(Invoke([this](const std::string& json) { + EXPECT_TRUE(component_manager_.LoadTraits(json, nullptr)); + })); + EXPECT_CALL(device_, SetStateProperties(_, _, _)) + .WillRepeatedly( + Invoke(&component_manager_, &ComponentManager::SetStateProperties)); + EXPECT_CALL(device_, SetStateProperty(_, _, _, _)) + .WillRepeatedly( + Invoke(&component_manager_, &ComponentManager::SetStateProperty)); + EXPECT_CALL(device_, AddComponent(_, _, _)) + .WillRepeatedly(Invoke([this](const std::string& name, + const std::vector& traits, + ErrorPtr* error) { + return component_manager_.AddComponent("", name, traits, error); + })); + + EXPECT_CALL(device_, + AddCommandHandler(_, AnyOf("_accessControlBlackList.block", + "_accessControlBlackList.unblock", + "_accessControlBlackList.list"), + _)) + .WillRepeatedly( + Invoke(&component_manager_, &ComponentManager::AddCommandHandler)); + + EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(0)); + + EXPECT_CALL(access_manager_, GetCapacity()).WillRepeatedly(Return(10)); + + handler_.reset(new AccessApiHandler{&device_, &access_manager_}); + } + + const base::DictionaryValue& AddCommand(const std::string& command) { + std::string id; + auto command_instance = component_manager_.ParseCommandInstance( + *test::CreateDictionaryValue(command.c_str()), Command::Origin::kLocal, + UserRole::kOwner, &id, nullptr); + EXPECT_NE(nullptr, command_instance.get()); + component_manager_.AddCommand(std::move(command_instance)); + EXPECT_EQ(Command::State::kDone, + component_manager_.FindCommand(id)->GetState()); + return component_manager_.FindCommand(id)->GetResults(); + } + + std::unique_ptr GetState() { + std::string path = + component_manager_.FindComponentWithTrait("_accessControlBlackList"); + EXPECT_FALSE(path.empty()); + const auto* component = component_manager_.FindComponent(path, nullptr); + EXPECT_TRUE(component); + const base::DictionaryValue* state = nullptr; + EXPECT_TRUE( + component->GetDictionary("state._accessControlBlackList", &state)); + return std::unique_ptr{state->DeepCopy()}; + } + + ComponentManagerImpl component_manager_; + StrictMock device_; + StrictMock access_manager_; + std::unique_ptr handler_; +}; + +TEST_F(AccessApiHandlerTest, Initialization) { + const base::DictionaryValue* trait = nullptr; + ASSERT_TRUE(component_manager_.GetTraits().GetDictionary( + "_accessControlBlackList", &trait)); + + auto expected = R"({ + "commands": { + "block": { + "minimalRole": "owner", + "parameters": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + }, + "expirationTimeoutSec": { + "type": "integer" + } + } + }, + "unblock": { + "minimalRole": "owner", + "parameters": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + } + } + }, + "list": { + "minimalRole": "owner", + "parameters": {}, + "results": { + "blackList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "applicationId": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + }, + "state": { + "size": { + "type": "integer", + "isRequired": true + }, + "capacity": { + "type": "integer", + "isRequired": true + } + } + })"; + EXPECT_JSON_EQ(expected, *trait); + + EXPECT_JSON_EQ((R"({ + "capacity": 10, + "size": 0 + })"), + *GetState()); +} + +TEST_F(AccessApiHandlerTest, Block) { + EXPECT_CALL(access_manager_, Block(std::vector{1, 2, 3}, + std::vector{3, 4, 5}, _, _)) + .WillOnce(WithArgs<3>( + Invoke([](const DoneCallback& callback) { callback.Run(nullptr); }))); + EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(1)); + + AddCommand(R"({ + 'name' : '_accessControlBlackList.block', + 'component': 'accessControl', + 'parameters': { + 'userId': 'AQID', + 'applicationId': 'AwQF', + 'expirationTimeoutSec': 1234 + } + })"); + EXPECT_JSON_EQ((R"({ + "capacity": 10, + "size": 1 + })"), + *GetState()); +} + +TEST_F(AccessApiHandlerTest, Unblock) { + EXPECT_CALL(access_manager_, Unblock(std::vector{1, 2, 3}, + std::vector{3, 4, 5}, _)) + .WillOnce(Return(true)); + EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4)); + + AddCommand(R"({ + 'name' : '_accessControlBlackList.unblock', + 'component': 'accessControl', + 'parameters': { + 'userId': 'AQID', + 'applicationId': 'AwQF', + 'expirationTimeoutSec': 1234 + } + })"); + EXPECT_JSON_EQ((R"({ + "capacity": 10, + "size": 4 + })"), + *GetState()); +} + +TEST_F(AccessApiHandlerTest, List) { + std::vector entries{ + {{11, 12, 13}, {21, 22, 23}, base::Time::FromTimeT(1410000000)}, + {{31, 32, 33}, {41, 42, 43}, base::Time::FromTimeT(1420000000)}, + }; + EXPECT_CALL(access_manager_, GetEntries()).WillOnce(Return(entries)); + EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4)); + + auto expected = R"({ + "blackList": [ { + "applicationId": "FRYX", + "userId": "CwwN" + }, { + "applicationId": "KSor", + "userId": "HyAh" + } ] + })"; + + EXPECT_JSON_EQ(expected, AddCommand(R"({ + 'name' : '_accessControlBlackList.list', + 'component': 'accessControl', + 'parameters': { + } + })")); +} +} // namespace weave diff --git a/src/access_black_list_manager.h b/src/access_black_list_manager.h new file mode 100644 index 0000000..ed30839 --- /dev/null +++ b/src/access_black_list_manager.h @@ -0,0 +1,37 @@ +// Copyright 2016 The Weave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_ +#define LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_ + +#include + +#include + +namespace weave { + +class AccessBlackListManager { + public: + struct Entry { + std::vector user_id; + std::vector app_id; + base::Time expiration; + }; + virtual ~AccessBlackListManager() = default; + + virtual void Block(const std::vector& user_id, + const std::vector& app_id, + const base::Time& expiration, + const DoneCallback& callback) = 0; + virtual bool Unblock(const std::vector& user_id, + const std::vector& app_id, + ErrorPtr* error) = 0; + virtual std::vector GetEntries() const = 0; + virtual size_t GetSize() const = 0; + virtual size_t GetCapacity() const = 0; +}; + +} // namespace weave + +#endif // LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_ diff --git a/src/base_api_handler.h b/src/base_api_handler.h index 1dbbac8..6eebfca 100644 --- a/src/base_api_handler.h +++ b/src/base_api_handler.h @@ -33,7 +33,7 @@ class BaseApiHandler final { void OnConfigChanged(const Settings& settings); DeviceRegistrationInfo* device_info_; - Device* device_; + Device* device_{nullptr}; base::WeakPtrFactory weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(BaseApiHandler); -- cgit v1.2.3 From 7329b749cdcdf17d0bdb0897d71d6795c3053dd8 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Tue, 19 Jan 2016 17:48:31 -0800 Subject: Fix unittest compilation on GCC Change-Id: Iae03516ee9d60f1da49762ccaaa28cebda486dc9 Reviewed-on: https://weave-review.googlesource.com/2221 Reviewed-by: Vitaly Buka --- file_lists.mk | 1 - src/access_api_handler_unittest.cc | 26 +++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/file_lists.mk b/file_lists.mk index e79372e..0f9abdb 100644 --- a/file_lists.mk +++ b/file_lists.mk @@ -165,4 +165,3 @@ THIRD_PARTY_LIBUWEAVE_SRC_FILES := \ third_party/libuweave/src/macaroon_caveat.c \ third_party/libuweave/src/macaroon_context.c \ third_party/libuweave/src/macaroon_encoding.c - diff --git a/src/access_api_handler_unittest.cc b/src/access_api_handler_unittest.cc index 3858722..44c83e2 100644 --- a/src/access_api_handler_unittest.cc +++ b/src/access_api_handler_unittest.cc @@ -169,11 +169,11 @@ TEST_F(AccessApiHandlerTest, Initialization) { })"; EXPECT_JSON_EQ(expected, *trait); - EXPECT_JSON_EQ((R"({ + expected = R"({ "capacity": 10, "size": 0 - })"), - *GetState()); + })"; + EXPECT_JSON_EQ(expected, *GetState()); } TEST_F(AccessApiHandlerTest, Block) { @@ -192,11 +192,12 @@ TEST_F(AccessApiHandlerTest, Block) { 'expirationTimeoutSec': 1234 } })"); - EXPECT_JSON_EQ((R"({ + + auto expected = R"({ "capacity": 10, "size": 1 - })"), - *GetState()); + })"; + EXPECT_JSON_EQ(expected, *GetState()); } TEST_F(AccessApiHandlerTest, Unblock) { @@ -214,11 +215,12 @@ TEST_F(AccessApiHandlerTest, Unblock) { 'expirationTimeoutSec': 1234 } })"); - EXPECT_JSON_EQ((R"({ + + auto expected = R"({ "capacity": 10, "size": 4 - })"), - *GetState()); + })"; + EXPECT_JSON_EQ(expected, *GetState()); } TEST_F(AccessApiHandlerTest, List) { @@ -239,11 +241,13 @@ TEST_F(AccessApiHandlerTest, List) { } ] })"; - EXPECT_JSON_EQ(expected, AddCommand(R"({ + const auto& results = AddCommand(R"({ 'name' : '_accessControlBlackList.list', 'component': 'accessControl', 'parameters': { } - })")); + })"); + + EXPECT_JSON_EQ(expected, results); } } // namespace weave -- cgit v1.2.3 From 81ac16e6bf71951354e79c709ca5500fda451f7f Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Thu, 21 Jan 2016 15:01:32 -0800 Subject: Add MockConfigStore argument to disabled default expectations Usually we need MockConfigStore just to pass into interface where store is needed. Default expectations are OK there. These expectations are not desired when we want to check data passed into Load/Save. Change-Id: Ia3ee784eec2fd12c4a3c81916f7cfb20f8d94e47 Reviewed-on: https://weave-review.googlesource.com/2274 Reviewed-by: Vitaly Buka --- include/weave/provider/test/mock_config_store.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/weave/provider/test/mock_config_store.h b/include/weave/provider/test/mock_config_store.h index e6411d6..a7eb374 100644 --- a/include/weave/provider/test/mock_config_store.h +++ b/include/weave/provider/test/mock_config_store.h @@ -18,10 +18,13 @@ namespace test { class MockConfigStore : public ConfigStore { public: - MockConfigStore() { + explicit MockConfigStore(bool set_expectations = true) { using testing::_; using testing::Return; + if (!set_expectations) + return; + EXPECT_CALL(*this, LoadDefaults(_)) .WillRepeatedly(testing::Invoke([](Settings* settings) { settings->firmware_version = "TEST_FIRMWARE"; @@ -39,8 +42,8 @@ class MockConfigStore : public ConfigStore { "version": 1, "device_id": "TEST_DEVICE_ID" })")); - EXPECT_CALL(*this, LoadSettings("config")).WillRepeatedly(Return("")); - EXPECT_CALL(*this, SaveSettings("config", _, _)) + EXPECT_CALL(*this, LoadSettings(_)).WillRepeatedly(Return("")); + EXPECT_CALL(*this, SaveSettings(_, _, _)) .WillRepeatedly(testing::WithArgs<1, 2>(testing::Invoke( [](const std::string& json, const DoneCallback& callback) { if (!callback.is_null()) -- cgit v1.2.3 From 484b6e4b808eb57f02253fbf63f117e259471c39 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Thu, 21 Jan 2016 15:05:36 -0800 Subject: Update AccessBlackListManager interface Added callback into Unblock method as this method also will save data. Added IsBlocked method. Added short explanation of how rules will work. BUG:25777658 BUG:25776798 Change-Id: I69b4575be261a0d54435bf07d95e6ac11290a1c7 Reviewed-on: https://weave-review.googlesource.com/2275 Reviewed-by: Vitaly Buka --- src/access_api_handler.cc | 10 +++------- src/access_api_handler_unittest.cc | 10 +++++++--- src/access_black_list_manager.h | 23 +++++++++++++++++++++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/access_api_handler.cc b/src/access_api_handler.cc index e03a548..9fa6df2 100644 --- a/src/access_api_handler.cc +++ b/src/access_api_handler.cc @@ -178,13 +178,9 @@ void AccessApiHandler::Unblock(const std::weak_ptr& cmd) { return; } - if (!manager_->Unblock(user_id, app_id, &error)) { - command->Abort(error.get(), nullptr); - return; - } - - UpdateState(); - command->Complete({}, nullptr); + manager_->Unblock(user_id, app_id, + base::Bind(&AccessApiHandler::OnCommandDone, + weak_ptr_factory_.GetWeakPtr(), cmd)); } void AccessApiHandler::List(const std::weak_ptr& cmd) { diff --git a/src/access_api_handler_unittest.cc b/src/access_api_handler_unittest.cc index 44c83e2..a142735 100644 --- a/src/access_api_handler_unittest.cc +++ b/src/access_api_handler_unittest.cc @@ -29,9 +29,12 @@ class MockAccessBlackListManager : public AccessBlackListManager { const base::Time&, const DoneCallback&)); MOCK_METHOD3(Unblock, - bool(const std::vector&, + void(const std::vector&, const std::vector&, - ErrorPtr*)); + const DoneCallback&)); + MOCK_CONST_METHOD2(IsBlocked, + bool(const std::vector&, + const std::vector&)); MOCK_CONST_METHOD0(GetEntries, std::vector()); MOCK_CONST_METHOD0(GetSize, size_t()); MOCK_CONST_METHOD0(GetCapacity, size_t()); @@ -203,7 +206,8 @@ TEST_F(AccessApiHandlerTest, Block) { TEST_F(AccessApiHandlerTest, Unblock) { EXPECT_CALL(access_manager_, Unblock(std::vector{1, 2, 3}, std::vector{3, 4, 5}, _)) - .WillOnce(Return(true)); + .WillOnce(WithArgs<2>( + Invoke([](const DoneCallback& callback) { callback.Run(nullptr); }))); EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4)); AddCommand(R"({ diff --git a/src/access_black_list_manager.h b/src/access_black_list_manager.h index ed30839..b56226a 100644 --- a/src/access_black_list_manager.h +++ b/src/access_black_list_manager.h @@ -14,8 +14,14 @@ namespace weave { class AccessBlackListManager { public: struct Entry { + // user_id is empty, app_id is empty: block everything. + // user_id is not empty, app_id is empty: block if user_id matches. + // user_id is empty, app_id is not empty: block if app_id matches. + // user_id is not empty, app_id is not empty: block if both match. std::vector user_id; std::vector app_id; + + // Time after which to discard the rule. base::Time expiration; }; virtual ~AccessBlackListManager() = default; @@ -24,14 +30,27 @@ class AccessBlackListManager { const std::vector& app_id, const base::Time& expiration, const DoneCallback& callback) = 0; - virtual bool Unblock(const std::vector& user_id, + virtual void Unblock(const std::vector& user_id, const std::vector& app_id, - ErrorPtr* error) = 0; + const DoneCallback& callback) = 0; + virtual bool IsBlocked(const std::vector& user_id, + const std::vector& app_id) const = 0; virtual std::vector GetEntries() const = 0; virtual size_t GetSize() const = 0; virtual size_t GetCapacity() const = 0; }; +inline bool operator==(const AccessBlackListManager::Entry& l, + const AccessBlackListManager::Entry& r) { + return l.user_id == r.user_id && l.app_id == r.app_id && + l.expiration == r.expiration; +} + +inline bool operator!=(const AccessBlackListManager::Entry& l, + const AccessBlackListManager::Entry& r) { + return !(l == r); +} + } // namespace weave #endif // LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_ -- cgit v1.2.3 From 5a7c4f55a0bdbd9c9e443d5b59414d1705919626 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Thu, 21 Jan 2016 15:09:34 -0800 Subject: Add black list manager implementation BUG:25776798 BUG:25777806 BUG:25777658 BUG:25777810 Change-Id: I86ec09e727360c1de4ae9af63f2ff1ab5f9cea1c Reviewed-on: https://weave-review.googlesource.com/2276 Reviewed-by: Vitaly Buka --- file_lists.mk | 2 + src/access_black_list_manager_impl.cc | 164 ++++++++++++++++++++++++ src/access_black_list_manager_impl.h | 58 +++++++++ src/access_black_list_manager_impl_unittest.cc | 167 +++++++++++++++++++++++++ src/device_manager.cc | 6 + src/device_manager.h | 4 + 6 files changed, 401 insertions(+) create mode 100644 src/access_black_list_manager_impl.cc create mode 100644 src/access_black_list_manager_impl.h create mode 100644 src/access_black_list_manager_impl_unittest.cc diff --git a/file_lists.mk b/file_lists.mk index 6356b2b..8dccd9c 100644 --- a/file_lists.mk +++ b/file_lists.mk @@ -4,6 +4,7 @@ WEAVE_SRC_FILES := \ src/access_api_handler.cc \ + src/access_black_list_manager_impl.cc \ src/backoff_entry.cc \ src/base_api_handler.cc \ src/commands/cloud_command_proxy.cc \ @@ -50,6 +51,7 @@ WEAVE_TEST_SRC_FILES := \ WEAVE_UNITTEST_SRC_FILES := \ src/access_api_handler_unittest.cc \ + src/access_black_list_manager_impl_unittest.cc \ src/backoff_entry_unittest.cc \ src/base_api_handler_unittest.cc \ src/commands/cloud_command_proxy_unittest.cc \ diff --git a/src/access_black_list_manager_impl.cc b/src/access_black_list_manager_impl.cc new file mode 100644 index 0000000..e6897ba --- /dev/null +++ b/src/access_black_list_manager_impl.cc @@ -0,0 +1,164 @@ +// Copyright 2016 The Weave 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 "src/access_black_list_manager_impl.h" + +#include +#include +#include + +#include "src/commands/schema_constants.h" +#include "src/data_encoding.h" + +namespace weave { + +namespace { +const char kConfigFileName[] = "black_list"; + +const char kUser[] = "user"; +const char kApp[] = "app"; +const char kExpiration[] = "expiration"; +} + +AccessBlackListManagerImpl::AccessBlackListManagerImpl( + provider::ConfigStore* store, + size_t capacity, + base::Clock* clock) + : capacity_{capacity}, clock_{clock}, store_{store} { + Load(); +} + +void AccessBlackListManagerImpl::Load() { + if (!store_) + return; + if (auto list = base::ListValue::From( + base::JSONReader::Read(store_->LoadSettings(kConfigFileName)))) { + for (const auto& e : *list) { + const base::DictionaryValue* entry{nullptr}; + std::string user; + std::string app; + decltype(entries_)::key_type key; + int expiration; + if (e->GetAsDictionary(&entry) && entry->GetString(kUser, &user) && + Base64Decode(user, &key.first) && entry->GetString(kApp, &app) && + Base64Decode(app, &key.second) && + entry->GetInteger(kExpiration, &expiration)) { + base::Time expiration_time = base::Time::FromTimeT(expiration); + if (expiration_time > clock_->Now()) + entries_[key] = expiration_time; + } + } + if (entries_.size() < list->GetSize()) { + // Save some storage space by saving without expired entries. + Save({}); + } + } +} + +void AccessBlackListManagerImpl::Save(const DoneCallback& callback) { + if (!store_) { + if (!callback.is_null()) + callback.Run(nullptr); + return; + } + + base::ListValue list; + for (const auto& e : entries_) { + scoped_ptr entry{new base::DictionaryValue}; + entry->SetString(kUser, Base64Encode(e.first.first)); + entry->SetString(kApp, Base64Encode(e.first.second)); + entry->SetInteger(kExpiration, e.second.ToTimeT()); + list.Append(std::move(entry)); + } + + std::string json; + base::JSONWriter::Write(list, &json); + store_->SaveSettings(kConfigFileName, json, callback); +} + +void AccessBlackListManagerImpl::RemoveExpired() { + for (auto i = begin(entries_); i != end(entries_);) { + if (i->second <= clock_->Now()) + i = entries_.erase(i); + else + ++i; + } +} + +void AccessBlackListManagerImpl::Block(const std::vector& user_id, + const std::vector& app_id, + const base::Time& expiration, + const DoneCallback& callback) { + // Iterating is OK as Save below is more expensive. + RemoveExpired(); + if (expiration <= clock_->Now()) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "aleady_expired", "Entry already expired"); + callback.Run(std::move(error)); + } + return; + } + if (entries_.size() >= capacity_) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "blacklist_is_full", "Unable to store more entries"); + callback.Run(std::move(error)); + } + return; + } + auto& value = entries_[std::make_pair(user_id, app_id)]; + value = std::max(value, expiration); + Save(callback); +} + +void AccessBlackListManagerImpl::Unblock(const std::vector& user_id, + const std::vector& app_id, + const DoneCallback& callback) { + if (!entries_.erase(std::make_pair(user_id, app_id))) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "entry_not_found", "Unknown entry"); + callback.Run(std::move(error)); + } + return; + } + // Iterating is OK as Save below is more expensive. + RemoveExpired(); + Save(callback); +} + +bool AccessBlackListManagerImpl::IsBlocked( + const std::vector& user_id, + const std::vector& app_id) const { + for (const auto& user : {{}, user_id}) { + for (const auto& app : {{}, app_id}) { + auto both = entries_.find(std::make_pair(user, app)); + if (both != end(entries_) && both->second > clock_->Now()) + return true; + } + } + return false; +} + +std::vector +AccessBlackListManagerImpl::GetEntries() const { + std::vector result; + for (const auto& e : entries_) + result.push_back({e.first.first, e.first.second, e.second}); + return result; +} + +size_t AccessBlackListManagerImpl::GetSize() const { + return entries_.size(); +} + +size_t AccessBlackListManagerImpl::GetCapacity() const { + return capacity_; +} + +} // namespace weave diff --git a/src/access_black_list_manager_impl.h b/src/access_black_list_manager_impl.h new file mode 100644 index 0000000..1c175db --- /dev/null +++ b/src/access_black_list_manager_impl.h @@ -0,0 +1,58 @@ +// Copyright 2016 The Weave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ +#define LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ + +#include +#include + +#include +#include +#include +#include + +#include "src/access_black_list_manager.h" + +namespace weave { + +class AccessBlackListManagerImpl : public AccessBlackListManager { + public: + explicit AccessBlackListManagerImpl(provider::ConfigStore* store, + size_t capacity = 1024, + base::Clock* clock = nullptr); + + // AccessBlackListManager implementation. + void Block(const std::vector& user_id, + const std::vector& app_id, + const base::Time& expiration, + const DoneCallback& callback) override; + void Unblock(const std::vector& user_id, + const std::vector& app_id, + const DoneCallback& callback) override; + bool IsBlocked(const std::vector& user_id, + const std::vector& app_id) const override; + std::vector GetEntries() const override; + size_t GetSize() const override; + size_t GetCapacity() const override; + + private: + void Load(); + void Save(const DoneCallback& callback); + void RemoveExpired(); + + const size_t capacity_{0}; + base::DefaultClock default_clock_; + base::Clock* clock_{&default_clock_}; + + provider::ConfigStore* store_{nullptr}; + std::map, std::vector>, base::Time> + entries_; + + DISALLOW_COPY_AND_ASSIGN(AccessBlackListManagerImpl); +}; + +} // namespace weave + +#endif // LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ diff --git a/src/access_black_list_manager_impl_unittest.cc b/src/access_black_list_manager_impl_unittest.cc new file mode 100644 index 0000000..2b6d66e --- /dev/null +++ b/src/access_black_list_manager_impl_unittest.cc @@ -0,0 +1,167 @@ +// Copyright 2016 The Weave 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 "src/access_black_list_manager_impl.h" + +#include +#include +#include +#include + +#include "src/test/mock_clock.h" +#include "src/bind_lambda.h" + +using testing::_; +using testing::Return; +using testing::StrictMock; + +namespace weave { + +class AccessBlackListManagerImplTest : public testing::Test { + protected: + void SetUp() { + std::string to_load = R"([{ + "user": "BQID", + "app": "BwQF", + "expiration": 1410000000 + }, { + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }])"; + + EXPECT_CALL(config_store_, LoadSettings("black_list")) + .WillOnce(Return(to_load)); + + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + std::string to_save = R"([{ + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }])"; + EXPECT_JSON_EQ(to_save, *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + + EXPECT_CALL(clock_, Now()) + .WillRepeatedly(Return(base::Time::FromTimeT(1412121212))); + manager_.reset(new AccessBlackListManagerImpl{&config_store_, 10, &clock_}); + } + StrictMock clock_; + StrictMock config_store_{false}; + std::unique_ptr manager_; +}; + +TEST_F(AccessBlackListManagerImplTest, Init) { + EXPECT_EQ(1u, manager_->GetSize()); + EXPECT_EQ(10u, manager_->GetCapacity()); + EXPECT_EQ((std::vector{{ + {1, 2, 3}, {3, 4, 5}, base::Time::FromTimeT(1419999999), + }}), + manager_->GetEntries()); +} + +TEST_F(AccessBlackListManagerImplTest, Block) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + std::string to_save = R"([{ + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }, { + "app": "CAgI", + "user": "BwcH", + "expiration": 1419990000 + }])"; + EXPECT_JSON_EQ(to_save, *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Block({7, 7, 7}, {8, 8, 8}, base::Time::FromTimeT(1419990000), {}); +} + +TEST_F(AccessBlackListManagerImplTest, BlockExpired) { + manager_->Block( + {}, {}, base::Time::FromTimeT(1400000000), base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "aleady_expired")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, BlockListIsFull) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillRepeatedly(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + if (!callback.is_null()) + callback.Run(nullptr); + }))); + for (size_t i = manager_->GetSize(); i < manager_->GetCapacity(); ++i) { + manager_->Block( + {99, static_cast(i / 256), static_cast(i % 256)}, + {8, 8, 8}, base::Time::FromTimeT(1419990000), {}); + EXPECT_EQ(i + 1, manager_->GetSize()); + } + manager_->Block( + {99}, {8, 8, 8}, base::Time::FromTimeT(1419990000), + base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "blacklist_is_full")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, Unblock) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + EXPECT_JSON_EQ("[]", *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Unblock({1, 2, 3}, {3, 4, 5}, {}); +} + +TEST_F(AccessBlackListManagerImplTest, UnblockNotFound) { + manager_->Unblock( + {5, 2, 3}, {5, 4, 5}, base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "entry_not_found")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, IsBlockedFalse) { + EXPECT_FALSE(manager_->IsBlocked({7, 7, 7}, {8, 8, 8})); +} + +class AccessBlackListManagerImplIsBlockedTest + : public AccessBlackListManagerImplTest, + public testing::WithParamInterface< + std::tuple, std::vector>> { + public: + void SetUp() override { + AccessBlackListManagerImplTest::SetUp(); + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<2>( + testing::Invoke([](const DoneCallback& callback) { + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Block(std::get<0>(GetParam()), std::get<1>(GetParam()), + base::Time::FromTimeT(1419990000), {}); + } +}; + +TEST_P(AccessBlackListManagerImplIsBlockedTest, IsBlocked) { + EXPECT_TRUE(manager_->IsBlocked({7, 7, 7}, {8, 8, 8})); +} + +INSTANTIATE_TEST_CASE_P( + Filters, + AccessBlackListManagerImplIsBlockedTest, + testing::Combine(testing::Values(std::vector{}, + std::vector{7, 7, 7}), + testing::Values(std::vector{}, + std::vector{8, 8, 8}))); + +} // namespace weave diff --git a/src/device_manager.cc b/src/device_manager.cc index 1158df7..50818ff 100644 --- a/src/device_manager.cc +++ b/src/device_manager.cc @@ -8,6 +8,8 @@ #include +#include "src/access_api_handler.h" +#include "src/access_black_list_manager_impl.h" #include "src/base_api_handler.h" #include "src/commands/schema_constants.h" #include "src/component_manager_impl.h" @@ -40,6 +42,10 @@ DeviceManager::DeviceManager(provider::ConfigStore* config_store, network, auth_manager_.get())); base_api_handler_.reset(new BaseApiHandler{device_info_.get(), this}); + black_list_manager_.reset(new AccessBlackListManagerImpl{config_store}); + access_api_handler_.reset( + new AccessApiHandler{this, black_list_manager_.get()}); + device_info_->Start(); if (http_server) { diff --git a/src/device_manager.h b/src/device_manager.h index d40ba8e..d77bacc 100644 --- a/src/device_manager.h +++ b/src/device_manager.h @@ -10,6 +10,8 @@ namespace weave { +class AccessApiHandler; +class AccessBlackListManager; class BaseApiHandler; class Config; class ComponentManager; @@ -107,6 +109,8 @@ class DeviceManager final : public Device { std::unique_ptr component_manager_; std::unique_ptr device_info_; std::unique_ptr base_api_handler_; + std::unique_ptr black_list_manager_; + std::unique_ptr access_api_handler_; std::unique_ptr privet_; base::WeakPtrFactory weak_ptr_factory_{this}; -- cgit v1.2.3 From 1c83377af2869fa3e641fa332fe2180aa6a21c2d Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Tue, 26 Jan 2016 14:05:01 -0800 Subject: Fix build errors introduced by https://weave-review.googlesource.com/#/c/2351/ Change-Id: I640173d59b76bb767d07ef2fbf484c8678543d18 Reviewed-on: https://weave-review.googlesource.com/2354 Reviewed-by: Alex Vakulenko --- src/access_api_handler.cc | 6 ++---- src/access_black_list_manager_impl.cc | 11 +++++------ src/access_black_list_manager_impl_unittest.cc | 24 +++++++++++------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/access_api_handler.cc b/src/access_api_handler.cc index 9fa6df2..7c39b20 100644 --- a/src/access_api_handler.cc +++ b/src/access_api_handler.cc @@ -32,8 +32,7 @@ bool GetIds(const base::DictionaryValue& parameters, std::string user_id; parameters.GetString(kUserId, &user_id); if (!Base64Decode(user_id, user_id_decoded)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidPropValue, + Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidPropValue, "Invalid user id '%s'", user_id.c_str()); return false; } @@ -41,8 +40,7 @@ bool GetIds(const base::DictionaryValue& parameters, std::string app_id; parameters.GetString(kApplicationId, &app_id); if (!Base64Decode(app_id, app_id_decoded)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidPropValue, + Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidPropValue, "Invalid app id '%s'", user_id.c_str()); return false; } diff --git a/src/access_black_list_manager_impl.cc b/src/access_black_list_manager_impl.cc index e6897ba..992a680 100644 --- a/src/access_black_list_manager_impl.cc +++ b/src/access_black_list_manager_impl.cc @@ -95,8 +95,8 @@ void AccessBlackListManagerImpl::Block(const std::vector& user_id, if (expiration <= clock_->Now()) { if (!callback.is_null()) { ErrorPtr error; - Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, - "aleady_expired", "Entry already expired"); + Error::AddTo(&error, FROM_HERE, "aleady_expired", + "Entry already expired"); callback.Run(std::move(error)); } return; @@ -104,8 +104,8 @@ void AccessBlackListManagerImpl::Block(const std::vector& user_id, if (entries_.size() >= capacity_) { if (!callback.is_null()) { ErrorPtr error; - Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, - "blacklist_is_full", "Unable to store more entries"); + Error::AddTo(&error, FROM_HERE, "blacklist_is_full", + "Unable to store more entries"); callback.Run(std::move(error)); } return; @@ -121,8 +121,7 @@ void AccessBlackListManagerImpl::Unblock(const std::vector& user_id, if (!entries_.erase(std::make_pair(user_id, app_id))) { if (!callback.is_null()) { ErrorPtr error; - Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, - "entry_not_found", "Unknown entry"); + Error::AddTo(&error, FROM_HERE, "entry_not_found", "Unknown entry"); callback.Run(std::move(error)); } return; diff --git a/src/access_black_list_manager_impl_unittest.cc b/src/access_black_list_manager_impl_unittest.cc index 2b6d66e..fd9f226 100644 --- a/src/access_black_list_manager_impl_unittest.cc +++ b/src/access_black_list_manager_impl_unittest.cc @@ -86,10 +86,10 @@ TEST_F(AccessBlackListManagerImplTest, Block) { } TEST_F(AccessBlackListManagerImplTest, BlockExpired) { - manager_->Block( - {}, {}, base::Time::FromTimeT(1400000000), base::Bind([](ErrorPtr error) { - EXPECT_TRUE(error->HasError("command_schema", "aleady_expired")); - })); + manager_->Block({}, {}, base::Time::FromTimeT(1400000000), + base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("aleady_expired")); + })); } TEST_F(AccessBlackListManagerImplTest, BlockListIsFull) { @@ -105,11 +105,10 @@ TEST_F(AccessBlackListManagerImplTest, BlockListIsFull) { {8, 8, 8}, base::Time::FromTimeT(1419990000), {}); EXPECT_EQ(i + 1, manager_->GetSize()); } - manager_->Block( - {99}, {8, 8, 8}, base::Time::FromTimeT(1419990000), - base::Bind([](ErrorPtr error) { - EXPECT_TRUE(error->HasError("command_schema", "blacklist_is_full")); - })); + manager_->Block({99}, {8, 8, 8}, base::Time::FromTimeT(1419990000), + base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("blacklist_is_full")); + })); } TEST_F(AccessBlackListManagerImplTest, Unblock) { @@ -124,10 +123,9 @@ TEST_F(AccessBlackListManagerImplTest, Unblock) { } TEST_F(AccessBlackListManagerImplTest, UnblockNotFound) { - manager_->Unblock( - {5, 2, 3}, {5, 4, 5}, base::Bind([](ErrorPtr error) { - EXPECT_TRUE(error->HasError("command_schema", "entry_not_found")); - })); + manager_->Unblock({5, 2, 3}, {5, 4, 5}, base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("entry_not_found")); + })); } TEST_F(AccessBlackListManagerImplTest, IsBlockedFalse) { -- cgit v1.2.3 From 7d29a5a5402badd1c24a8542539306a521c14cc1 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 14:21:37 -0800 Subject: Update macaroon lib Change-Id: I71f2aacbc094b3e4cbd10898d037719ac59c4015 Reviewed-on: https://weave-review.googlesource.com/2359 Reviewed-by: Vitaly Buka --- third_party/libuweave/src/crypto_hmac.h | 30 +- third_party/libuweave/src/crypto_utils.c | 7 - third_party/libuweave/src/macaroon.c | 286 ++++++---- third_party/libuweave/src/macaroon.h | 89 ++-- third_party/libuweave/src/macaroon_caveat.c | 591 ++++++++++++++++----- third_party/libuweave/src/macaroon_caveat.h | 105 +++- .../libuweave/src/macaroon_caveat_internal.h | 41 ++ third_party/libuweave/src/macaroon_context.c | 24 +- third_party/libuweave/src/macaroon_context.h | 18 +- third_party/libuweave/src/macaroon_encoding.c | 103 ++-- third_party/libuweave/src/macaroon_encoding.h | 47 +- 11 files changed, 974 insertions(+), 367 deletions(-) create mode 100644 third_party/libuweave/src/macaroon_caveat_internal.h diff --git a/third_party/libuweave/src/crypto_hmac.h b/third_party/libuweave/src/crypto_hmac.h index bac634a..6f76ed0 100644 --- a/third_party/libuweave/src/crypto_hmac.h +++ b/third_party/libuweave/src/crypto_hmac.h @@ -9,21 +9,21 @@ #include #include -// Return the minimum required number of bytes for the state_buffer used in the -// init, update and final functions. -size_t uw_crypto_hmac_required_buffer_size_(); +typedef struct { + const uint8_t* bytes; + size_t num_bytes; +} UwCryptoHmacMsg; -bool uw_crypto_hmac_init_(uint8_t* state_buffer, - size_t state_buffer_len, - const uint8_t* key, - size_t key_len); -bool uw_crypto_hmac_update_(uint8_t* state_buffer, - size_t state_buffer_len, - const uint8_t* data, - size_t data_len); -bool uw_crypto_hmac_final_(uint8_t* state_buffer, - size_t state_buffer_len, - uint8_t* truncated_digest, - size_t truncated_digest_len); +/** + * Compute HMAC over a list of messages, which is equivalent to computing HMAC + * over the concatenation of all the messages. The HMAC output will be truncated + * to the desired length truncated_digest_len, and written into trucated_digest. + */ +bool uw_crypto_hmac_(const uint8_t* key, + size_t key_len, + const UwCryptoHmacMsg messages[], + size_t num_messages, + uint8_t* truncated_digest, + size_t truncated_digest_len); #endif // LIBUWEAVE_SRC_CRYPTO_HMAC_H_ diff --git a/third_party/libuweave/src/crypto_utils.c b/third_party/libuweave/src/crypto_utils.c index 76b8068..7a6e38f 100644 --- a/third_party/libuweave/src/crypto_utils.c +++ b/third_party/libuweave/src/crypto_utils.c @@ -7,13 +7,6 @@ bool uw_crypto_utils_equal_(const uint8_t* arr1, const uint8_t* arr2, size_t len) { - if (arr1 == NULL || arr2 == NULL) { - if (arr1 == NULL && arr2 == NULL && len == 0) { - return true; - } - return false; - } - uint8_t diff = 0; for (size_t i = 0; i < len; i++) { diff |= arr1[i] ^ arr2[i]; diff --git a/third_party/libuweave/src/macaroon.c b/third_party/libuweave/src/macaroon.c index 70afda1..50b2d00 100644 --- a/third_party/libuweave/src/macaroon.c +++ b/third_party/libuweave/src/macaroon.c @@ -8,13 +8,17 @@ #include "src/crypto_utils.h" #include "src/macaroon_caveat.h" +#include "src/macaroon_caveat_internal.h" #include "src/macaroon_encoding.h" -static bool create_mac_tag_(const uint8_t* key, size_t key_len, - const UwMacaroonCaveat* caveats, size_t num_caveats, +static bool create_mac_tag_(const uint8_t* key, + size_t key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* const caveats[], + size_t num_caveats, uint8_t mac_tag[UW_MACAROON_MAC_LEN]) { - if (key == NULL || key_len == 0 || caveats == NULL || num_caveats == 0 || - mac_tag == NULL) { + if (key == NULL || key_len == 0 || context == NULL || caveats == NULL || + num_caveats == 0 || mac_tag == NULL) { return false; } @@ -26,15 +30,15 @@ static bool create_mac_tag_(const uint8_t* key, size_t key_len, uint8_t mac_tag_buff[UW_MACAROON_MAC_LEN]; // Compute the first tag by using the key - if (!uw_macaroon_caveat_sign_(key, key_len, &(caveats[0]), mac_tag_buff, + if (!uw_macaroon_caveat_sign_(key, key_len, context, caveats[0], mac_tag_buff, UW_MACAROON_MAC_LEN)) { return false; } // Compute the rest of the tags by using the tag as the key for (size_t i = 1; i < num_caveats; i++) { - if (!uw_macaroon_caveat_sign_(mac_tag_buff, UW_MACAROON_MAC_LEN, - &(caveats[i]), mac_tag_buff, + if (!uw_macaroon_caveat_sign_(mac_tag_buff, UW_MACAROON_MAC_LEN, context, + caveats[i], mac_tag_buff, UW_MACAROON_MAC_LEN)) { return false; } @@ -44,33 +48,38 @@ static bool create_mac_tag_(const uint8_t* key, size_t key_len, return true; } -bool uw_macaroon_new_from_mac_tag_(UwMacaroon* new_macaroon, - const uint8_t mac_tag[UW_MACAROON_MAC_LEN], - const UwMacaroonCaveat* caveats, - size_t num_caveats) { - if (new_macaroon == NULL || mac_tag == NULL || caveats == NULL || - num_caveats == 0) { +static bool verify_mac_tag_(const uint8_t* root_key, + size_t root_key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* const caveats[], + size_t num_caveats, + const uint8_t mac_tag[UW_MACAROON_MAC_LEN]) { + if (root_key == NULL || root_key_len == 0 || context == NULL || + caveats == NULL || num_caveats == 0 || mac_tag == 0) { return false; } - memcpy(new_macaroon->mac_tag, mac_tag, UW_MACAROON_MAC_LEN); - new_macaroon->num_caveats = num_caveats; - new_macaroon->caveats = caveats; + uint8_t computed_mac_tag[UW_MACAROON_MAC_LEN] = {0}; + if (!create_mac_tag_(root_key, root_key_len, context, caveats, num_caveats, + computed_mac_tag)) { + return false; + } - return true; + return uw_crypto_utils_equal_(mac_tag, computed_mac_tag, UW_MACAROON_MAC_LEN); } -bool uw_macaroon_new_from_root_key_(UwMacaroon* new_macaroon, - const uint8_t* root_key, - size_t root_key_len, - const UwMacaroonCaveat* caveats, - size_t num_caveats) { - if (new_macaroon == NULL || root_key == NULL || root_key_len == 0 || - caveats == NULL || num_caveats == 0) { +bool uw_macaroon_create_from_root_key_(UwMacaroon* new_macaroon, + const uint8_t* root_key, + size_t root_key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* const caveats[], + size_t num_caveats) { + if (new_macaroon == NULL || root_key == NULL || context == NULL || + root_key_len == 0 || caveats == NULL || num_caveats == 0) { return false; } - if (!create_mac_tag_(root_key, root_key_len, caveats, num_caveats, + if (!create_mac_tag_(root_key, root_key_len, context, caveats, num_caveats, new_macaroon->mac_tag)) { return false; } @@ -81,139 +90,228 @@ bool uw_macaroon_new_from_root_key_(UwMacaroon* new_macaroon, return true; } -bool uw_macaroon_verify_(const UwMacaroon* macaroon, - const uint8_t* root_key, - size_t root_key_len) { - if (macaroon == NULL || root_key == NULL) { - return false; - } - - uint8_t mac_tag[UW_MACAROON_MAC_LEN] = {0}; - if (!create_mac_tag_(root_key, root_key_len, macaroon->caveats, - macaroon->num_caveats, mac_tag)) { - return false; - } - - return uw_crypto_utils_equal_(mac_tag, macaroon->mac_tag, - UW_MACAROON_MAC_LEN); -} - bool uw_macaroon_extend_(const UwMacaroon* old_macaroon, UwMacaroon* new_macaroon, + const UwMacaroonContext* context, const UwMacaroonCaveat* additional_caveat, - uint8_t* buffer, size_t buffer_size) { - if (old_macaroon == NULL || new_macaroon == NULL || + uint8_t* buffer, + size_t buffer_size) { + if (old_macaroon == NULL || new_macaroon == NULL || context == NULL || additional_caveat == NULL || buffer == NULL || buffer_size == 0) { return false; } new_macaroon->num_caveats = old_macaroon->num_caveats + 1; - // Extend the caveat list - if ((new_macaroon->num_caveats) * sizeof(UwMacaroonCaveat) > buffer_size) { - // Not enough memory to store the extended caveat list + // Extend the caveat pointer list + if ((new_macaroon->num_caveats) * sizeof(UwMacaroonCaveat*) > buffer_size) { + // Not enough memory to store the extended caveat pointer list return false; } - UwMacaroonCaveat* extended_list = (UwMacaroonCaveat*)buffer; - if (old_macaroon->caveats != NULL && extended_list != old_macaroon->caveats) { + const UwMacaroonCaveat** extended_list = (const UwMacaroonCaveat**)buffer; + if (new_macaroon->caveats != old_macaroon->caveats) { memcpy(extended_list, old_macaroon->caveats, - (old_macaroon->num_caveats) * sizeof(UwMacaroonCaveat)); + old_macaroon->num_caveats * sizeof(old_macaroon->caveats[0])); } - extended_list[old_macaroon->num_caveats] = *additional_caveat; - new_macaroon->caveats = extended_list; + extended_list[old_macaroon->num_caveats] = additional_caveat; + new_macaroon->caveats = (const UwMacaroonCaveat* const*)extended_list; // Compute the new MAC tag - return create_mac_tag_(old_macaroon->mac_tag, UW_MACAROON_MAC_LEN, - additional_caveat, 1, new_macaroon->mac_tag); + return create_mac_tag_(old_macaroon->mac_tag, UW_MACAROON_MAC_LEN, context, + new_macaroon->caveats + old_macaroon->num_caveats, 1, + new_macaroon->mac_tag); } -// Encode a Macaroon to a byte string -bool uw_macaroon_dump_(const UwMacaroon* macaroon, - uint8_t* out, - size_t out_len, - size_t* resulting_str_len) { - if (macaroon == NULL || out == NULL || out_len == 0 || - resulting_str_len == NULL) { +static void init_validation_result(UwMacaroonValidationResult* result) { + // Start from the largest scope + result->granted_scope = kUwMacaroonCaveatScopeTypeOwner; + result->expiration_time = UINT32_MAX; + result->weave_app_restricted = false; + result->lan_session_id = NULL; + result->lan_session_id_len = 0; + result->num_delegatees = 0; +} + +/** Reset the result object to the lowest scope when encountering errors */ +static void reset_validation_result(UwMacaroonValidationResult* result) { + // Start from the largest scope or highest privilege + result->granted_scope = + (UwMacaroonCaveatScopeType)UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE; + result->expiration_time = 0; + result->weave_app_restricted = true; + result->lan_session_id = NULL; + result->lan_session_id_len = 0; + + result->num_delegatees = 0; + for (size_t i = 0; i < MAX_NUM_DELEGATEES; i++) { + result->delegatees[i].id = NULL; + result->delegatees[i].id_len = 0; + result->delegatees[i].is_app = true; + } +} + +bool uw_macaroon_validate_(const UwMacaroon* macaroon, + const uint8_t* root_key, + size_t root_key_len, + const UwMacaroonContext* context, + UwMacaroonValidationResult* result) { + if (result == NULL) { return false; } + init_validation_result(result); - size_t offset = 0, item_len; + if (root_key == NULL || root_key_len == 0 || macaroon == NULL || + context == NULL || result == NULL || + !verify_mac_tag_(root_key, root_key_len, context, macaroon->caveats, + macaroon->num_caveats, macaroon->mac_tag)) { + return false; + } - if (!uw_macaroon_encoding_encode_byte_str_( - macaroon->mac_tag, UW_MACAROON_MAC_LEN, out, out_len, &item_len)) { + UwMacaroonValidationState state; + if (!uw_macaroon_caveat_init_validation_state_(&state)) { return false; } - offset += item_len; + for (size_t i = 0; i < macaroon->num_caveats; i++) { + if (!uw_macaroon_caveat_validate_(macaroon->caveats[i], context, &state, + result)) { + reset_validation_result(result); // Reset the result object + return false; + } + } + + return true; +} - if (!uw_macaroon_encoding_encode_array_len_( - (uint32_t)(macaroon->num_caveats), out + offset, out_len - offset, &item_len)) { +// Encode a Macaroon to a byte string +bool uw_macaroon_serialize_(const UwMacaroon* macaroon, + uint8_t* out, + size_t out_len, + size_t* resulting_str_len) { + if (macaroon == NULL || out == NULL || + out_len < UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN || + resulting_str_len == NULL) { + return false; + } + + // Need to encode the whole Macaroon again into a byte string. + + // First encode the part without the overall byte string header to the buffer + // to get the total length. + size_t item_len = 0; + // Start with an offset + size_t offset = UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + if (!uw_macaroon_encoding_encode_array_len_((uint32_t)(macaroon->num_caveats), + out + offset, out_len - offset, + &item_len)) { return false; } offset += item_len; for (size_t i = 0; i < macaroon->num_caveats; i++) { if (!uw_macaroon_encoding_encode_byte_str_( - macaroon->caveats[i].bytes, macaroon->caveats[i].num_bytes, + macaroon->caveats[i]->bytes, macaroon->caveats[i]->num_bytes, out + offset, out_len - offset, &item_len)) { return false; } offset += item_len; } - *resulting_str_len = offset; + if (!uw_macaroon_encoding_encode_byte_str_(macaroon->mac_tag, + UW_MACAROON_MAC_LEN, out + offset, + out_len - offset, &item_len)) { + return false; + } + offset += item_len; + + // Encode the length of the body at the beginning of the buffer + size_t bstr_len = offset - UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + if (!uw_macaroon_encoding_encode_byte_str_len_( + bstr_len, out, UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN, &item_len)) { + return false; + } + + // Move the body part to be adjacent to the byte string header part + memmove(out + item_len, out + UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN, + bstr_len); + + *resulting_str_len = item_len + bstr_len; return true; } // Decode a byte string to a Macaroon -bool uw_macaroon_load_(const uint8_t* in, - size_t in_len, - uint8_t* caveats_buffer, - size_t caveats_buffer_size, - UwMacaroon* macaroon) { - if (in == NULL || in_len == 0 || caveats_buffer == NULL || - caveats_buffer_size == 0 || macaroon == NULL) { +bool uw_macaroon_deserialize_(const uint8_t* in, + size_t in_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroon* macaroon) { + if (in == NULL || in_len == 0 || buffer == NULL || buffer_size == 0 || + macaroon == NULL) { return false; } - const uint8_t* tag; - size_t tag_len; - if (!uw_macaroon_encoding_decode_byte_str_(in, in_len, &tag, &tag_len) || - tag_len != UW_MACAROON_MAC_LEN) { + size_t offset = 0; + size_t item_len = 0; + + const uint8_t* bstr = NULL; + size_t bstr_len = 0; + if (!uw_macaroon_encoding_decode_byte_str_(in + offset, in_len - offset, + &bstr, &bstr_len)) { return false; } - memcpy(macaroon->mac_tag, tag, UW_MACAROON_MAC_LEN); + item_len = bstr - in; // The length of the first byte string header + offset += item_len; - size_t offset = 0, cbor_item_len; - if (!uw_macaroon_encoding_get_item_len_(in, in_len, &cbor_item_len)) { + if (item_len + bstr_len != in_len) { + // The string length doesn't match return false; } - offset += cbor_item_len; - uint32_t array_len; + uint32_t array_len = 0; if (!uw_macaroon_encoding_decode_array_len_(in + offset, in_len - offset, &array_len)) { return false; } macaroon->num_caveats = (size_t)array_len; - if (caveats_buffer_size < array_len * sizeof(UwMacaroonCaveat)) { + if (buffer_size < + (array_len * (sizeof(UwMacaroonCaveat) + sizeof(UwMacaroonCaveat*)))) { + // Need two levels of abstraction, one for structs and one for pointers + return false; + } + + if (!uw_macaroon_encoding_get_item_len_(in + offset, in_len - offset, + &item_len)) { return false; } + offset += item_len; - UwMacaroonCaveat* caveats = (UwMacaroonCaveat*)caveats_buffer; + const UwMacaroonCaveat** caveat_pointers = (const UwMacaroonCaveat**)buffer; + buffer += array_len * sizeof(UwMacaroonCaveat*); + UwMacaroonCaveat* caveat_structs = (UwMacaroonCaveat*)buffer; for (size_t i = 0; i < array_len; i++) { - if (!uw_macaroon_encoding_get_item_len_(in + offset, in_len - offset, - &cbor_item_len)) { + caveat_pointers[i] = &(caveat_structs[i]); + + if (!uw_macaroon_encoding_decode_byte_str_( + in + offset, in_len - offset, &(caveat_structs[i].bytes), + &(caveat_structs[i].num_bytes))) { return false; } - offset += cbor_item_len; - if (!uw_macaroon_encoding_decode_byte_str_(in + offset, in_len - offset, - &(caveats[i].bytes), - &(caveats[i].num_bytes))) { + if (!uw_macaroon_encoding_get_item_len_(in + offset, in_len - offset, + &item_len)) { return false; } + offset += item_len; + } + macaroon->caveats = caveat_pointers; + + const uint8_t* tag; + size_t tag_len; + if (!uw_macaroon_encoding_decode_byte_str_(in + offset, in_len - offset, &tag, + &tag_len) || + tag_len != UW_MACAROON_MAC_LEN) { + return false; } - macaroon->caveats = caveats; + memcpy(macaroon->mac_tag, tag, UW_MACAROON_MAC_LEN); return true; } diff --git a/third_party/libuweave/src/macaroon.h b/third_party/libuweave/src/macaroon.h index 61242f7..dfaaba7 100644 --- a/third_party/libuweave/src/macaroon.h +++ b/third_party/libuweave/src/macaroon.h @@ -9,7 +9,8 @@ #include #include -#include "macaroon_caveat.h" +#include "src/macaroon_caveat.h" +#include "src/macaroon_context.h" #define UW_MACAROON_MAC_LEN 16 @@ -20,45 +21,71 @@ typedef struct { uint8_t mac_tag[UW_MACAROON_MAC_LEN]; size_t num_caveats; - const UwMacaroonCaveat* caveats; + const UwMacaroonCaveat* const* caveats; } UwMacaroon; -bool uw_macaroon_new_from_mac_tag_(UwMacaroon* new_macaroon, - const uint8_t mac_tag[UW_MACAROON_MAC_LEN], - const UwMacaroonCaveat* caveats, - size_t num_caveats); +// For the delegatee list in the validation result object +typedef struct { + const uint8_t* id; + size_t id_len; + bool is_app; +} UwMacaroonDelegateeInfo; + +#define MAX_NUM_DELEGATEES 10 -bool uw_macaroon_new_from_root_key_(UwMacaroon* new_macaroon, - const uint8_t* root_key, - size_t root_key_len, - const UwMacaroonCaveat* caveats, - size_t num_caveats); +typedef struct { + UwMacaroonCaveatScopeType granted_scope; + uint32_t expiration_time; + bool weave_app_restricted; + const uint8_t* lan_session_id; + size_t lan_session_id_len; + UwMacaroonDelegateeInfo delegatees[MAX_NUM_DELEGATEES]; + size_t num_delegatees; +} UwMacaroonValidationResult; -bool uw_macaroon_verify_(const UwMacaroon* macaroon, - const uint8_t* root_key, - size_t root_key_len); +bool uw_macaroon_create_from_root_key_(UwMacaroon* new_macaroon, + const uint8_t* root_key, + size_t root_key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* const caveats[], + size_t num_caveats); -// Create a new macaroon with a new caveat +/** Creates a new macaroon with a new caveat. */ bool uw_macaroon_extend_(const UwMacaroon* old_macaroon, UwMacaroon* new_macaroon, + const UwMacaroonContext* context, const UwMacaroonCaveat* additional_caveat, - uint8_t* buffer, size_t buffer_size); + uint8_t* buffer, + size_t buffer_size); + +/** + * Verify and validate the Macaroon, and put relevant information into the + * result object. + */ +bool uw_macaroon_validate_( + const UwMacaroon* macaroon, + const uint8_t* root_key, + size_t root_key_len, + const UwMacaroonContext* context, + UwMacaroonValidationResult* result); -// Encode a Macaroon to a byte string -bool uw_macaroon_dump_(const UwMacaroon* macaroon, - uint8_t* out, - size_t out_len, - size_t* resulting_str_len); +/** Encode a Macaroon to a byte string. */ +bool uw_macaroon_serialize_(const UwMacaroon* macaroon, + uint8_t* out, + size_t out_len, + size_t* resulting_str_len); -// Decode a byte string to a Macaroon (the caveats_buffer here is used only for -// the caveat pointer list *caveats in the UwMacaroon *macaroon). One note is -// that the function doesn't copy string values to new buffers, so the caller -// may maintain the input string around to make caveats with string values to -// be usuable. -bool uw_macaroon_load_(const uint8_t* in, - size_t in_len, - uint8_t* caveats_buffer, - size_t caveats_buffer_size, - UwMacaroon* macaroon); +/** + * Decodes a byte string to a Macaroon. + * + * One note is that the function doesn't copy string values to new buffers, so + * the caller must maintain the input string around to make caveats with string + * values to be usable. + */ +bool uw_macaroon_deserialize_(const uint8_t* in, + size_t in_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroon* new_macaroon); #endif // LIBUWEAVE_SRC_MACAROON_H_ diff --git a/third_party/libuweave/src/macaroon_caveat.c b/third_party/libuweave/src/macaroon_caveat.c index 594f9de..b8b2183 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -3,125 +3,287 @@ // found in the LICENSE file. #include "src/macaroon_caveat.h" +#include "src/macaroon_caveat_internal.h" #include #include "src/crypto_hmac.h" +#include "src/macaroon.h" #include "src/macaroon_context.h" #include "src/macaroon_encoding.h" -// TODO(bozhu): Find a better way to pre-allocate memory for HMACc computations? -// Are C99 variable-length arrays allowed on embedded devices? -#define HMAC_STATE_BUFFER_SIZE 1024 +#define MAX_CBOR_STR_LEN_FOR_UINT 5 -static bool create_caveat_(UwMacaroonCaveatType type, const void* value, - size_t value_len, uint8_t* buffer, - size_t buffer_size, UwMacaroonCaveat* caveat) { - if (buffer == NULL || buffer_size == 0 || caveat == NULL) { - // Here value can be NULL, and value_len can be 0 - return false; +static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { + switch (type) { + case kUwMacaroonCaveatTypeNonce: + case kUwMacaroonCaveatTypeScope: + case kUwMacaroonCaveatTypeExpirationAbsolute: + case kUwMacaroonCaveatTypeTTL1Hour: + case kUwMacaroonCaveatTypeTTL24Hour: + case kUwMacaroonCaveatTypeDelegationTimestamp: + case kUwMacaroonCaveatTypeDelegateeUser: + case kUwMacaroonCaveatTypeDelegateeApp: + case kUwMacaroonCaveatTypeAppCommandsOnly: + case kUwMacaroonCaveatTypeBleSessionID: + case kUwMacaroonCaveatTypeLanSessionID: + case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: + case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: + return true; } + return false; +} - caveat->bytes = buffer; - size_t encoded_str_len, total_str_len; +static bool is_valid_scope_type_(UwMacaroonCaveatScopeType type) { + switch (type) { + case kUwMacaroonCaveatScopeTypeOwner: + case kUwMacaroonCaveatScopeTypeManager: + case kUwMacaroonCaveatScopeTypeUser: + case kUwMacaroonCaveatScopeTypeViewer: + return true; + } + return false; +} - uint32_t unsigned_int = (uint32_t)type; - if (!uw_macaroon_encoding_encode_uint_(unsigned_int, buffer, buffer_size, - &encoded_str_len)) { +static bool create_caveat_no_value_(UwMacaroonCaveatType type, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + // (buffer_size == 0 || get_buffer_size_() > buffer_size) will conver the case + // that get_buffer_size_() returns 0 (for errors), so there is no need to + // check get_buffer_size_() == 0 again. + if (buffer == NULL || buffer_size == 0 || new_caveat == NULL || + uw_macaroon_caveat_creation_get_buffsize_(type, 0) > buffer_size) { return false; } - total_str_len = encoded_str_len; - buffer += encoded_str_len; - buffer_size -= encoded_str_len; - - switch (type) { - case kUwMacaroonCaveatTypeStop: - case kUwMacaroonCaveatTypeSessionIdentifier: - // No value - encoded_str_len = 0; - break; - - case kUwMacaroonCaveatTypeScope: - case kUwMacaroonCaveatTypeIssued: - case kUwMacaroonCaveatTypeTTL: - case kUwMacaroonCaveatTypeExpiration: - // Integer - if (value_len != sizeof(uint32_t)) { - // Wrong size for integers - return false; - } - unsigned_int = *((uint32_t*)value); - if (!uw_macaroon_encoding_encode_uint_(unsigned_int, buffer, buffer_size, - &encoded_str_len)) { - return false; - } - break; - case kUwMacaroonCaveatTypeIdentifier: - // Text string - if (!uw_macaroon_encoding_encode_text_str_((uint8_t*)value, value_len, - buffer, buffer_size, - &encoded_str_len)) { - return false; - } - break; - - default: - // Should never reach here - return false; + size_t encoded_str_len = 0, total_str_len = 0; + if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, + &encoded_str_len)) { + return false; } - total_str_len += encoded_str_len; - caveat->num_bytes = total_str_len; + + new_caveat->bytes = buffer; + new_caveat->num_bytes = total_str_len; return true; } -bool uw_macaroon_caveat_create_without_value_(UwMacaroonCaveatType type, - uint8_t* buffer, - size_t buffer_size, - UwMacaroonCaveat* caveat) { - if (buffer == NULL || buffer_size == 0 || caveat == NULL) { +static bool create_caveat_uint_value_(UwMacaroonCaveatType type, + uint32_t unsigned_int, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + if (buffer == NULL || buffer_size == 0 || new_caveat == NULL || + uw_macaroon_caveat_creation_get_buffsize_(type, 0) > buffer_size) { return false; } - if (type != kUwMacaroonCaveatTypeStop && - type != kUwMacaroonCaveatTypeSessionIdentifier) { + + size_t encoded_str_len = 0, total_str_len = 0; + if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, + &encoded_str_len)) { + return false; + } + total_str_len += encoded_str_len; + if (!uw_macaroon_encoding_encode_uint_(unsigned_int, buffer + total_str_len, + buffer_size - total_str_len, + &encoded_str_len)) { return false; } + total_str_len += encoded_str_len; - return create_caveat_(type, NULL, 0, buffer, buffer_size, caveat); + new_caveat->bytes = buffer; + new_caveat->num_bytes = total_str_len; + return true; } -bool uw_macaroon_caveat_create_with_uint_(UwMacaroonCaveatType type, - uint32_t value, uint8_t* buffer, - size_t buffer_size, - UwMacaroonCaveat* caveat) { - if (buffer == NULL || buffer_size == 0 || caveat == NULL) { +static bool create_caveat_bstr_value_(UwMacaroonCaveatType type, + const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + if ((str == NULL && str_len != 0) || buffer == NULL || buffer_size == 0 || + new_caveat == NULL || + uw_macaroon_caveat_creation_get_buffsize_(type, str_len) > buffer_size) { return false; } - if (type != kUwMacaroonCaveatTypeScope && - type != kUwMacaroonCaveatTypeIssued && - type != kUwMacaroonCaveatTypeTTL && - type != kUwMacaroonCaveatTypeExpiration) { + + size_t encoded_str_len = 0, total_str_len = 0; + if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, + &encoded_str_len)) { return false; } + total_str_len += encoded_str_len; + if (!uw_macaroon_encoding_encode_byte_str_( + str, str_len, buffer + total_str_len, buffer_size - total_str_len, + &encoded_str_len)) { + return false; + } + total_str_len += encoded_str_len; - return create_caveat_(type, &value, sizeof(uint32_t), buffer, buffer_size, - caveat); + new_caveat->bytes = buffer; + new_caveat->num_bytes = total_str_len; + return true; } -bool uw_macaroon_caveat_create_with_str_(UwMacaroonCaveatType type, - const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, - UwMacaroonCaveat* caveat) { - if (buffer == NULL || buffer_size == 0 || caveat == NULL || - (str == NULL && str_len != 0)) { - return false; +size_t uw_macaroon_caveat_creation_get_buffsize_(UwMacaroonCaveatType type, + size_t str_len) { + switch (type) { + // No values + case kUwMacaroonCaveatTypeTTL1Hour: + case kUwMacaroonCaveatTypeTTL24Hour: + case kUwMacaroonCaveatTypeAppCommandsOnly: + case kUwMacaroonCaveatTypeBleSessionID: + return UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + + // Unsigned integers + case kUwMacaroonCaveatTypeScope: + case kUwMacaroonCaveatTypeExpirationAbsolute: + case kUwMacaroonCaveatTypeDelegationTimestamp: + return 2 * UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + + // Byte strings + case kUwMacaroonCaveatTypeNonce: + case kUwMacaroonCaveatTypeDelegateeUser: + case kUwMacaroonCaveatTypeDelegateeApp: + case kUwMacaroonCaveatTypeLanSessionID: + case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: + case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: + return str_len + UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + + default: + return 0; // For errors } - if (type != kUwMacaroonCaveatTypeIdentifier) { +} + +bool uw_macaroon_caveat_create_nonce_(const uint8_t* nonce, + size_t nonce_size, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_bstr_value_(kUwMacaroonCaveatTypeNonce, nonce, + nonce_size, buffer, buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_scope_(UwMacaroonCaveatScopeType scope, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + if (!is_valid_scope_type_(scope)) { return false; } - return create_caveat_(type, str, str_len, buffer, buffer_size, caveat); + return create_caveat_uint_value_(kUwMacaroonCaveatTypeScope, scope, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_expiration_absolute_( + uint32_t expiration_time, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_uint_value_(kUwMacaroonCaveatTypeExpirationAbsolute, + expiration_time, buffer, buffer_size, + new_caveat); +} + +bool uw_macaroon_caveat_create_ttl_1_hour_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_no_value_(kUwMacaroonCaveatTypeTTL1Hour, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_ttl_24_hour_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_no_value_(kUwMacaroonCaveatTypeTTL24Hour, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_delegation_timestamp_( + uint32_t timestamp, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_uint_value_(kUwMacaroonCaveatTypeDelegationTimestamp, + timestamp, buffer, buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_delegatee_user_(const uint8_t* id_str, + size_t id_str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeUser, id_str, + id_str_len, buffer, buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_delegatee_app_(const uint8_t* id_str, + size_t id_str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeApp, id_str, + id_str_len, buffer, buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_app_commands_only_( + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_no_value_(kUwMacaroonCaveatTypeAppCommandsOnly, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_ble_session_id_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_no_value_(kUwMacaroonCaveatTypeBleSessionID, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_lan_session_id_(const uint8_t* session_id, + size_t session_id_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_bstr_value_(kUwMacaroonCaveatTypeLanSessionID, + session_id, session_id_len, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_client_authorization_token_( + const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + if (str_len == 0) { + return create_caveat_no_value_( + kUwMacaroonCaveatTypeClientAuthorizationTokenV1, buffer, buffer_size, + new_caveat); + } + return create_caveat_bstr_value_( + kUwMacaroonCaveatTypeClientAuthorizationTokenV1, str, str_len, buffer, + buffer_size, new_caveat); +} + +bool uw_macaroon_caveat_create_server_authentication_token_( + const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + if (str_len == 0) { + return create_caveat_no_value_( + kUwMacaroonCaveatTypeServerAuthenticationTokenV1, buffer, buffer_size, + new_caveat); + } + return create_caveat_bstr_value_( + kUwMacaroonCaveatTypeServerAuthenticationTokenV1, str, str_len, buffer, + buffer_size, new_caveat); } bool uw_macaroon_caveat_get_type_(const UwMacaroonCaveat* caveat, @@ -137,20 +299,208 @@ bool uw_macaroon_caveat_get_type_(const UwMacaroonCaveat* caveat, } *type = (UwMacaroonCaveatType)unsigned_int; + return is_valid_caveat_type_(*type); +} + +/* === Some internal functions defined in macaroon_caveat_internal.h === */ - if (*type != kUwMacaroonCaveatTypeStop && - *type != kUwMacaroonCaveatTypeScope && - *type != kUwMacaroonCaveatTypeIdentifier && - *type != kUwMacaroonCaveatTypeIssued && - *type != kUwMacaroonCaveatTypeTTL && - *type != kUwMacaroonCaveatTypeExpiration && - *type != kUwMacaroonCaveatTypeSessionIdentifier) { +bool uw_macaroon_caveat_sign_(const uint8_t* key, + size_t key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* caveat, + uint8_t* mac_tag, + size_t mac_tag_size) { + if (key == NULL || key_len == 0 || context == NULL || caveat == NULL || + mac_tag == NULL || mac_tag_size == 0) { + return false; + } + + UwMacaroonCaveatType caveat_type; + if (!uw_macaroon_caveat_get_type_(caveat, &caveat_type) || + !is_valid_caveat_type_(caveat_type)) { return false; } + // Need to encode the whole caveat as a byte string and then sign it + + // If there is no additional value from the context, just compute the HMAC on + // the current byte string. + uint8_t bstr_cbor_prefix[UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN] = {0}; + size_t bstr_cbor_prefix_len = 0; + if (caveat_type != kUwMacaroonCaveatTypeBleSessionID) { + if (!uw_macaroon_encoding_encode_byte_str_len_( + (uint32_t)(caveat->num_bytes), bstr_cbor_prefix, + sizeof(bstr_cbor_prefix), &bstr_cbor_prefix_len)) { + return false; + } + + UwCryptoHmacMsg messages[] = { + {bstr_cbor_prefix, bstr_cbor_prefix_len}, + {caveat->bytes, caveat->num_bytes}, + }; + + return uw_crypto_hmac_(key, key_len, messages, + sizeof(messages) / sizeof(messages[0]), mac_tag, + mac_tag_size); + } + + // If there is additional value from the context. + if (context->ble_session_id == NULL || context->ble_session_id_len == 0) { + return false; + } + + // The length here includes the length of the BLE session ID string. + if (!uw_macaroon_encoding_encode_byte_str_len_( + (uint32_t)(context->ble_session_id_len + caveat->num_bytes), + bstr_cbor_prefix, sizeof(bstr_cbor_prefix), &bstr_cbor_prefix_len)) { + return false; + } + + uint8_t value_cbor_prefix[UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN] = {0}; + size_t value_cbor_prefix_len = 0; + if (!uw_macaroon_encoding_encode_byte_str_len_( + (uint32_t)(context->ble_session_id_len), value_cbor_prefix, + sizeof(value_cbor_prefix), &value_cbor_prefix_len)) { + return false; + } + + UwCryptoHmacMsg messages[] = { + {bstr_cbor_prefix, bstr_cbor_prefix_len}, + {caveat->bytes, caveat->num_bytes}, + {value_cbor_prefix, value_cbor_prefix_len}, + {context->ble_session_id, context->ble_session_id_len}, + }; + + return uw_crypto_hmac_(key, key_len, messages, + sizeof(messages) / sizeof(messages[0]), mac_tag, + mac_tag_size); +} + +static bool update_and_check_expiration_time( + uint32_t current_time, + uint32_t new_expiration_time, + UwMacaroonValidationResult* result) { + if (result->expiration_time > new_expiration_time) { + result->expiration_time = new_expiration_time; + } + + return current_time <= result->expiration_time; +} + +static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, + const UwMacaroonCaveat* caveat, + UwMacaroonValidationResult* result) { + if (result->num_delegatees >= MAX_NUM_DELEGATEES) { + return false; + } + bool is_app = (caveat_type == kUwMacaroonCaveatTypeDelegateeApp); + + if (is_app) { + for (size_t i = 0; i < result->num_delegatees; i++) { + // There must have at most one DelegateeApp + if (result->delegatees[i].is_app) { + return false; + } + } + } + + if (!uw_macaroon_caveat_get_value_bstr_( + caveat, &(result->delegatees[result->num_delegatees].id), + &(result->delegatees[result->num_delegatees].id_len))) { + return false; + } + result->delegatees[result->num_delegatees].is_app = is_app; + result->num_delegatees++; return true; } +bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, + const UwMacaroonContext* context, + UwMacaroonValidationState* state, + UwMacaroonValidationResult* result) { + if (caveat == NULL || context == NULL || state == NULL || result == NULL) { + return false; + } + + uint32_t expiration_time = 0; + uint32_t issued_time = 0; + uint32_t scope = UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE; + + UwMacaroonCaveatType caveat_type; + if (!uw_macaroon_caveat_get_type_(caveat, &caveat_type)) { + return false; + } + + switch (caveat_type) { + // The types that always validate + case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: + case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: + case kUwMacaroonCaveatTypeNonce: + case kUwMacaroonCaveatTypeBleSessionID: + return true; + + case kUwMacaroonCaveatTypeDelegationTimestamp: + state->has_issued_time = true; + if (!uw_macaroon_caveat_get_value_uint_(caveat, &issued_time)) { + return false; + } + state->issued_time = issued_time; + return true; + + case kUwMacaroonCaveatTypeTTL1Hour: + if (!(state->has_issued_time)) { + return false; + } + return update_and_check_expiration_time( + context->current_time, state->issued_time + 60 * 60, result); + + case kUwMacaroonCaveatTypeTTL24Hour: + if (!(state->has_issued_time)) { + return false; + } + return update_and_check_expiration_time( + context->current_time, state->issued_time + 24 * 60 * 60, result); + + // Need to create a list of delegatees + case kUwMacaroonCaveatTypeDelegateeUser: + return update_delegatee_list(caveat_type, caveat, result); + + case kUwMacaroonCaveatTypeDelegateeApp: + return update_delegatee_list(caveat_type, caveat, result); + + // Time related caveats + case kUwMacaroonCaveatTypeExpirationAbsolute: + if (!uw_macaroon_caveat_get_value_uint_(caveat, &expiration_time)) { + return false; + } + return update_and_check_expiration_time(context->current_time, + expiration_time, result); + + // The caveats that update the values of the result object + case kUwMacaroonCaveatTypeScope: + if (!uw_macaroon_caveat_get_value_uint_(caveat, &scope) || + // Larger value means less priviledge + scope < UW_MACAROON_CAVEAT_SCOPE_HIGHEST_POSSIBLE || + scope > UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE) { + return false; + } + if (scope > (uint32_t)(result->granted_scope)) { + result->granted_scope = (UwMacaroonCaveatScopeType)scope; + } + return true; + + case kUwMacaroonCaveatTypeAppCommandsOnly: + result->weave_app_restricted = true; + return true; + + case kUwMacaroonCaveatTypeLanSessionID: + return uw_macaroon_caveat_get_value_bstr_( + caveat, &(result->lan_session_id), &(result->lan_session_id_len)); + } + + return false; +} + bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, uint32_t* unsigned_int) { if (caveat == NULL || unsigned_int == NULL) { @@ -162,13 +512,13 @@ bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, return false; } if (type != kUwMacaroonCaveatTypeScope && - type != kUwMacaroonCaveatTypeIssued && - type != kUwMacaroonCaveatTypeTTL && - type != kUwMacaroonCaveatTypeExpiration) { + type != kUwMacaroonCaveatTypeExpirationAbsolute && + type != kUwMacaroonCaveatTypeDelegationTimestamp) { // Wrong type return false; } + // Skip the portion for CBOR type size_t offset; if (!uw_macaroon_encoding_get_item_len_(caveat->bytes, caveat->num_bytes, &offset)) { @@ -179,8 +529,9 @@ bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, caveat->bytes + offset, caveat->num_bytes - offset, unsigned_int); } -bool uw_macaroon_caveat_get_value_str_(const UwMacaroonCaveat* caveat, - const uint8_t** str, size_t* str_len) { +bool uw_macaroon_caveat_get_value_bstr_(const UwMacaroonCaveat* caveat, + const uint8_t** str, + size_t* str_len) { if (caveat == NULL || str == NULL || str_len == NULL) { return false; } @@ -189,7 +540,12 @@ bool uw_macaroon_caveat_get_value_str_(const UwMacaroonCaveat* caveat, if (!uw_macaroon_caveat_get_type_(caveat, &type)) { return false; } - if (type != kUwMacaroonCaveatTypeIdentifier) { + if (type != kUwMacaroonCaveatTypeNonce && + type != kUwMacaroonCaveatTypeDelegateeUser && + type != kUwMacaroonCaveatTypeDelegateeApp && + type != kUwMacaroonCaveatTypeLanSessionID && + type != kUwMacaroonCaveatTypeClientAuthorizationTokenV1 && + type != kUwMacaroonCaveatTypeServerAuthenticationTokenV1) { // Wrong type return false; } @@ -200,48 +556,17 @@ bool uw_macaroon_caveat_get_value_str_(const UwMacaroonCaveat* caveat, return false; } - return uw_macaroon_encoding_decode_text_str_( + return uw_macaroon_encoding_decode_byte_str_( caveat->bytes + offset, caveat->num_bytes - offset, str, str_len); } -bool uw_macaroon_caveat_sign_(const uint8_t* key, size_t key_len, - const UwMacaroonCaveat* caveat, uint8_t* mac_tag, - size_t mac_tag_size) { - if (key == NULL || key_len == 0 || caveat == NULL || mac_tag == NULL || - mac_tag_size == 0) { - return false; - } - - uint8_t hmac_state_buffer[HMAC_STATE_BUFFER_SIZE]; - if (HMAC_STATE_BUFFER_SIZE < uw_crypto_hmac_required_buffer_size_()) { +bool uw_macaroon_caveat_init_validation_state_( + UwMacaroonValidationState* state) { + if (state == NULL) { return false; } - if (!uw_crypto_hmac_init_(hmac_state_buffer, HMAC_STATE_BUFFER_SIZE, key, - key_len)) { - return false; - } - - if (!uw_crypto_hmac_update_(hmac_state_buffer, HMAC_STATE_BUFFER_SIZE, - caveat->bytes, caveat->num_bytes)) { - return false; - } - - const uint8_t* context; - size_t context_len; - UwMacaroonCaveatType caveat_type; - - if ((!uw_macaroon_caveat_get_type_(caveat, &caveat_type)) || - (!uw_macaroon_context_get_(caveat_type, &context, &context_len))) { - return false; - } - if (context != NULL && context_len != 0) { - if (!uw_crypto_hmac_update_(hmac_state_buffer, HMAC_STATE_BUFFER_SIZE, - context, context_len)) { - return false; - } - } - - return uw_crypto_hmac_final_(hmac_state_buffer, HMAC_STATE_BUFFER_SIZE, - mac_tag, mac_tag_size); + state->has_issued_time = false; + state->issued_time = 0; + return true; } diff --git a/third_party/libuweave/src/macaroon_caveat.h b/third_party/libuweave/src/macaroon_caveat.h index 2e01742..9baf70d 100644 --- a/third_party/libuweave/src/macaroon_caveat.h +++ b/third_party/libuweave/src/macaroon_caveat.h @@ -15,15 +15,25 @@ typedef struct { } UwMacaroonCaveat; typedef enum { - kUwMacaroonCaveatTypeStop = 0, - kUwMacaroonCaveatTypeScope = 1, - kUwMacaroonCaveatTypeIdentifier = 2, - kUwMacaroonCaveatTypeIssued = 3, - kUwMacaroonCaveatTypeTTL = 4, - kUwMacaroonCaveatTypeExpiration = 5, - kUwMacaroonCaveatTypeSessionIdentifier = 16, + kUwMacaroonCaveatTypeNonce = 0, // bstr + kUwMacaroonCaveatTypeScope = 1, // uint + kUwMacaroonCaveatTypeExpirationAbsolute = 5, // uint + kUwMacaroonCaveatTypeTTL1Hour = 6, // no value + kUwMacaroonCaveatTypeTTL24Hour = 7, // no value + kUwMacaroonCaveatTypeDelegationTimestamp = 8, // uint + kUwMacaroonCaveatTypeDelegateeUser = 9, // bstr + kUwMacaroonCaveatTypeDelegateeApp = 10, // bstr + kUwMacaroonCaveatTypeAppCommandsOnly = 11, // no value + kUwMacaroonCaveatTypeBleSessionID = 16, // no value + kUwMacaroonCaveatTypeLanSessionID = 17, // bstr + kUwMacaroonCaveatTypeClientAuthorizationTokenV1 = 8193, // bstr (0x2001) + kUwMacaroonCaveatTypeServerAuthenticationTokenV1 = 12289, // bstr (0x3001) } UwMacaroonCaveatType; +// For security sanity checks +#define UW_MACAROON_CAVEAT_SCOPE_HIGHEST_POSSIBLE 0 +#define UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE 127 + typedef enum { kUwMacaroonCaveatScopeTypeOwner = 2, kUwMacaroonCaveatScopeTypeManager = 8, @@ -31,28 +41,75 @@ typedef enum { kUwMacaroonCaveatScopeTypeViewer = 20, } UwMacaroonCaveatScopeType; -bool uw_macaroon_caveat_create_without_value_(UwMacaroonCaveatType type, +/** Compute the buffer sizes that are enough for caveat creation functions. */ +size_t uw_macaroon_caveat_creation_get_buffsize_(UwMacaroonCaveatType type, + size_t str_len); + +// Caveat creation functions +bool uw_macaroon_caveat_create_nonce_(const uint8_t* nonce, + size_t nonce_size, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_scope_(UwMacaroonCaveatScopeType scope, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_expiration_absolute_( + uint32_t expiration_time, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_ttl_1_hour_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_ttl_24_hour_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_delegation_timestamp_( + uint32_t timestamp, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_delegatee_user_(const uint8_t* id_str, + size_t id_str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_delegatee_app_(const uint8_t* id_str, + size_t id_str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat); -bool uw_macaroon_caveat_create_with_uint_(UwMacaroonCaveatType type, - uint32_t value, uint8_t* buffer, - size_t buffer_size, - UwMacaroonCaveat* new_caveat); -bool uw_macaroon_caveat_create_with_str_(UwMacaroonCaveatType type, - const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, - UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_app_commands_only_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_ble_session_id_(uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_lan_session_id_(const uint8_t* session_id, + size_t session_id_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); + +// The string values for these two token types are optional. +// Use str_len = 0 to indicate creating the caveats without string values. +bool uw_macaroon_caveat_create_client_authorization_token_( + const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_server_authentication_token_( + const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); +/** Get the type for the given caveat. */ bool uw_macaroon_caveat_get_type_(const UwMacaroonCaveat* caveat, UwMacaroonCaveatType* type); -bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, - uint32_t* unsigned_int); -bool uw_macaroon_caveat_get_value_str_(const UwMacaroonCaveat* caveat, - const uint8_t** str, size_t* str_len); - -bool uw_macaroon_caveat_sign_(const uint8_t* key, size_t key_len, - const UwMacaroonCaveat* caveat, uint8_t* mac_tag, - size_t mac_tag_size); #endif // LIBUWEAVE_SRC_MACAROON_CAVEAT_H_ diff --git a/third_party/libuweave/src/macaroon_caveat_internal.h b/third_party/libuweave/src/macaroon_caveat_internal.h new file mode 100644 index 0000000..46a72fb --- /dev/null +++ b/third_party/libuweave/src/macaroon_caveat_internal.h @@ -0,0 +1,41 @@ +// Copyright 2015 The Weave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBUWEAVE_SRC_MACAROON_CAVEAT_INTERNAL_H_ +#define LIBUWEAVE_SRC_MACAROON_CAVEAT_INTERNAL_H_ + +#include +#include +#include + +#include "src/macaroon.h" +#include "src/macaroon_caveat.h" + +bool uw_macaroon_caveat_sign_(const uint8_t* key, + size_t key_len, + const UwMacaroonContext* context, + const UwMacaroonCaveat* caveat, + uint8_t* mac_tag, + size_t mac_tag_size); + +typedef struct { + bool has_issued_time; + uint32_t issued_time; +} UwMacaroonValidationState; + +bool uw_macaroon_caveat_init_validation_state_( + UwMacaroonValidationState* state); + +bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, + const UwMacaroonContext* context, + UwMacaroonValidationState* state, + UwMacaroonValidationResult* result); + +bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, + uint32_t* unsigned_int); +bool uw_macaroon_caveat_get_value_bstr_(const UwMacaroonCaveat* caveat, + const uint8_t** str, + size_t* str_len); + +#endif // LIBUWEAVE_SRC_MACAROON_CAVEAT_INTERNAL_H_ diff --git a/third_party/libuweave/src/macaroon_context.c b/third_party/libuweave/src/macaroon_context.c index 7477784..2f1685d 100644 --- a/third_party/libuweave/src/macaroon_context.c +++ b/third_party/libuweave/src/macaroon_context.c @@ -4,19 +4,19 @@ #include "src/macaroon_context.h" -#include "src/macaroon_caveat.h" - -bool uw_macaroon_context_get_(UwMacaroonCaveatType type, - const uint8_t** context, size_t* context_len) { - if (type != kUwMacaroonCaveatTypeSessionIdentifier) { - *context = NULL; - *context_len = 0; +bool uw_macaroon_context_create_(uint32_t current_time, + const uint8_t* ble_session_id, + size_t ble_session_id_len, + UwMacaroonContext* new_context) { + if (ble_session_id == NULL && ble_session_id_len != 0) { + return false; + } + if (new_context == NULL) { + return false; } - // TODO(bozhu): Waiting for a proper way to obtain the session identifier. - // Have we already implemented something related to session identifiers? - *context = NULL; - *context_len = 0; - + new_context->current_time = current_time; + new_context->ble_session_id = ble_session_id; + new_context->ble_session_id_len = ble_session_id_len; return true; } diff --git a/third_party/libuweave/src/macaroon_context.h b/third_party/libuweave/src/macaroon_context.h index 8522b69..c230eb7 100644 --- a/third_party/libuweave/src/macaroon_context.h +++ b/third_party/libuweave/src/macaroon_context.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UW_LIBUWEAVE_SRC_MACAROON_CONTEXT_ -#define UW_LIBUWEAVE_SRC_MACAROON_CONTEXT_ +#ifndef LIBUWEAVE_SRC_MACAROON_CONTEXT_ +#define LIBUWEAVE_SRC_MACAROON_CONTEXT_ #include #include @@ -11,7 +11,15 @@ #include "src/macaroon_caveat.h" -bool uw_macaroon_context_get_(UwMacaroonCaveatType type, - const uint8_t** context, size_t* context_len); +typedef struct { + uint32_t current_time; // In number of seconds since Jan 1st 2000 00:00:00 + const uint8_t* ble_session_id; // Only for BLE + size_t ble_session_id_len; +} UwMacaroonContext; -#endif // UW_LIBUWEAVE_SRC_MACAROON_CONTEXT_ +bool uw_macaroon_context_create_(uint32_t current_time, + const uint8_t* ble_session_id, + size_t ble_session_id_len, + UwMacaroonContext* new_context); + +#endif // LIBUWEAVE_SRC_MACAROON_CONTEXT_ diff --git a/third_party/libuweave/src/macaroon_encoding.c b/third_party/libuweave/src/macaroon_encoding.c index 3fb5323..29adc52 100644 --- a/third_party/libuweave/src/macaroon_encoding.c +++ b/third_party/libuweave/src/macaroon_encoding.c @@ -21,28 +21,34 @@ typedef enum { kCborMajorTypeArray = 4 << 5, // type 4 -- arrays } CborMajorType; -// -- Prototypes begin -- static inline CborMajorType get_type_(const uint8_t* cbor); static inline uint8_t get_addtl_data_(const uint8_t* cbor); static inline void set_type_(CborMajorType type, uint8_t* cbor); static inline void set_addtl_data_(uint8_t addtl_data, uint8_t* cbor); -// Compute the minimum number of bytes to store the unsigned integer. +/** Computes the minimum number of bytes to store the unsigned integer. */ static inline size_t uint_min_len_(uint32_t unsigned_int); -// Encoding or decoding without checking types -static bool blindly_encode_uint_(uint32_t unsigned_int, uint8_t* buffer, - size_t buffer_size, size_t* result_len); -static bool blindly_encode_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +/** Encoding or decoding without checking types */ +static bool blindly_encode_uint_(uint32_t unsigned_int, + uint8_t* buffer, + size_t buffer_size, + size_t* result_len); +static bool blindly_encode_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* result_len); -static bool blindly_decode_uint_(const uint8_t* cbor, size_t cbor_len, +static bool blindly_decode_uint_(const uint8_t* cbor, + size_t cbor_len, uint32_t* unsigned_int); -static bool blindly_decode_str_(const uint8_t* cbor, size_t cbor_len, - const uint8_t** out_str, size_t* out_str_len); -// -- Prototypes end -- +static bool blindly_decode_str_(const uint8_t* cbor, + size_t cbor_len, + const uint8_t** out_str, + size_t* out_str_len); -bool uw_macaroon_encoding_get_item_len_(const uint8_t* cbor, size_t cbor_len, +bool uw_macaroon_encoding_get_item_len_(const uint8_t* cbor, + size_t cbor_len, size_t* first_item_len) { if (cbor == NULL || cbor_len == 0 || first_item_len == NULL) { return false; @@ -76,7 +82,8 @@ bool uw_macaroon_encoding_get_item_len_(const uint8_t* cbor, size_t cbor_len, } bool uw_macaroon_encoding_encode_uint_(const uint32_t unsigned_int, - uint8_t* buffer, size_t buffer_size, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len) { if (buffer == NULL || buffer_size == 0 || resulting_cbor_len == NULL) { return false; @@ -88,7 +95,8 @@ bool uw_macaroon_encoding_encode_uint_(const uint32_t unsigned_int, } bool uw_macaroon_encoding_encode_array_len_(const uint32_t array_len, - uint8_t* buffer, size_t buffer_size, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len) { if (buffer == NULL || buffer_size == 0 || resulting_cbor_len == NULL) { return false; @@ -99,8 +107,10 @@ bool uw_macaroon_encoding_encode_array_len_(const uint32_t array_len, resulting_cbor_len); } -bool uw_macaroon_encoding_encode_byte_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +bool uw_macaroon_encoding_encode_byte_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len) { if (buffer == NULL || buffer_size == 0 || resulting_cbor_len == NULL) { return false; @@ -111,8 +121,10 @@ bool uw_macaroon_encoding_encode_byte_str_(const uint8_t* str, size_t str_len, resulting_cbor_len); } -bool uw_macaroon_encoding_encode_text_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +bool uw_macaroon_encoding_encode_text_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len) { if (buffer == NULL || buffer_size == 0 || resulting_cbor_len == NULL) { return false; @@ -123,7 +135,19 @@ bool uw_macaroon_encoding_encode_text_str_(const uint8_t* str, size_t str_len, resulting_cbor_len); } -bool uw_macaroon_encoding_decode_uint_(const uint8_t* cbor, size_t cbor_len, +bool uw_macaroon_encoding_encode_byte_str_len_(size_t str_len, + uint8_t* buffer, + size_t buffer_size, + size_t* resulting_cbor_len) { + if (buffer == NULL || buffer_size == 0 || resulting_cbor_len == NULL) { + return false; + } + set_type_(kCborMajorTypeByteStr, buffer); + return blindly_encode_uint_(str_len, buffer, buffer_size, resulting_cbor_len); +} + +bool uw_macaroon_encoding_decode_uint_(const uint8_t* cbor, + size_t cbor_len, uint32_t* unsigned_int) { if (cbor == NULL || cbor_len == 0 || unsigned_int == NULL || get_type_(cbor) != kCborMajorTypeUint) { @@ -144,7 +168,8 @@ bool uw_macaroon_encoding_decode_array_len_(const uint8_t* cbor, return blindly_decode_uint_(cbor, cbor_len, array_len); } -bool uw_macaroon_encoding_decode_byte_str_(const uint8_t* cbor, size_t cbor_len, +bool uw_macaroon_encoding_decode_byte_str_(const uint8_t* cbor, + size_t cbor_len, const uint8_t** out_str, size_t* out_str_len) { if (cbor == NULL || cbor_len == 0 || out_str == NULL || out_str_len == NULL || @@ -155,7 +180,8 @@ bool uw_macaroon_encoding_decode_byte_str_(const uint8_t* cbor, size_t cbor_len, return blindly_decode_str_(cbor, cbor_len, out_str, out_str_len); } -bool uw_macaroon_encoding_decode_text_str_(const uint8_t* cbor, size_t cbor_len, +bool uw_macaroon_encoding_decode_text_str_(const uint8_t* cbor, + size_t cbor_len, const uint8_t** out_str, size_t* out_str_len) { if (cbor == NULL || cbor_len == 0 || out_str == NULL || out_str_len == NULL || @@ -193,9 +219,12 @@ static inline size_t uint_min_len_(uint32_t unsigned_int) { return 4; } -// Write the unsigned int in the big-endian fashion by using the minimum number -// of bytes in CBOR -static inline bool write_uint_big_endian_(uint32_t unsigned_int, uint8_t* buff, +/** + * Writes the unsigned int in the big-endian fashion by using the minimum number + * of bytes in CBOR + */ +static inline bool write_uint_big_endian_(uint32_t unsigned_int, + uint8_t* buff, size_t buff_len) { if (buff == NULL || buff_len == 0) { return false; @@ -225,8 +254,9 @@ static inline bool write_uint_big_endian_(uint32_t unsigned_int, uint8_t* buff, return true; } -// Read the unsigned int written in big-endian -static inline bool read_uint_big_endian_(const uint8_t* bytes, size_t num_bytes, +/** Reads the unsigned int written in big-endian. */ +static inline bool read_uint_big_endian_(const uint8_t* bytes, + size_t num_bytes, uint32_t* unsigned_int) { if (bytes == NULL || num_bytes == 0 || num_bytes > 4 || unsigned_int == NULL) { @@ -252,8 +282,10 @@ static inline bool read_uint_big_endian_(const uint8_t* bytes, size_t num_bytes, return true; } -static bool blindly_encode_uint_(uint32_t unsigned_int, uint8_t* buffer, - size_t buffer_size, size_t* result_len) { +static bool blindly_encode_uint_(uint32_t unsigned_int, + uint8_t* buffer, + size_t buffer_size, + size_t* result_len) { if (buffer == NULL || buffer_size == 0 || result_len == NULL) { return false; } @@ -288,8 +320,10 @@ static bool blindly_encode_uint_(uint32_t unsigned_int, uint8_t* buffer, return write_uint_big_endian_(unsigned_int, buffer + 1, buffer_size - 1); } -static bool blindly_encode_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +static bool blindly_encode_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* result_len) { if (buffer == NULL || buffer_size == 0) { return false; @@ -320,7 +354,8 @@ static bool blindly_encode_str_(const uint8_t* str, size_t str_len, return true; } -static bool blindly_decode_uint_(const uint8_t* cbor, size_t cbor_len, +static bool blindly_decode_uint_(const uint8_t* cbor, + size_t cbor_len, uint32_t* unsigned_int) { if (cbor == NULL || cbor_len == 0 || unsigned_int == NULL) { return false; @@ -344,8 +379,10 @@ static bool blindly_decode_uint_(const uint8_t* cbor, size_t cbor_len, return read_uint_big_endian_(cbor + 1, uint_num_bytes, unsigned_int); } -static bool blindly_decode_str_(const uint8_t* cbor, size_t cbor_len, - const uint8_t** out_str, size_t* out_str_len) { +static bool blindly_decode_str_(const uint8_t* cbor, + size_t cbor_len, + const uint8_t** out_str, + size_t* out_str_len) { if (cbor == NULL || cbor_len == 0 || out_str == NULL || out_str == NULL) { return false; } diff --git a/third_party/libuweave/src/macaroon_encoding.h b/third_party/libuweave/src/macaroon_encoding.h index edddfc1..60f80a6 100644 --- a/third_party/libuweave/src/macaroon_encoding.h +++ b/third_party/libuweave/src/macaroon_encoding.h @@ -17,32 +17,53 @@ #include #include -// Get the number of bytes that is occupied by the first data item in the give -// CBOR string. -bool uw_macaroon_encoding_get_item_len_(const uint8_t* cbor, size_t cbor_len, +#define UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN 5 + +/** + * Gets the number of bytes that is occupied by the first data item in the give + * CBOR string. + */ +bool uw_macaroon_encoding_get_item_len_(const uint8_t* cbor, + size_t cbor_len, size_t* first_item_len); bool uw_macaroon_encoding_encode_uint_(const uint32_t unsigned_int, - uint8_t* buffer, size_t buffer_size, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len); bool uw_macaroon_encoding_encode_array_len_(const uint32_t array_len, - uint8_t* buffer, size_t buffer_size, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len); -bool uw_macaroon_encoding_encode_byte_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +bool uw_macaroon_encoding_encode_byte_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len); -bool uw_macaroon_encoding_encode_text_str_(const uint8_t* str, size_t str_len, - uint8_t* buffer, size_t buffer_size, +bool uw_macaroon_encoding_encode_text_str_(const uint8_t* str, + size_t str_len, + uint8_t* buffer, + size_t buffer_size, size_t* resulting_cbor_len); -bool uw_macaroon_encoding_decode_uint_(const uint8_t* cbor, size_t cbor_len, +/** Only encode the header (major type and length) of the byte string */ +bool uw_macaroon_encoding_encode_byte_str_len_(size_t str_len, + uint8_t* buffer, + size_t buffer_size, + size_t* resulting_cbor_len); + +bool uw_macaroon_encoding_decode_uint_(const uint8_t* cbor, + size_t cbor_len, uint32_t* unsigned_int); bool uw_macaroon_encoding_decode_array_len_(const uint8_t* cbor, - size_t cbor_len, uint32_t* array_len); -bool uw_macaroon_encoding_decode_byte_str_(const uint8_t* cbor, size_t cbor_len, + size_t cbor_len, + uint32_t* array_len); +bool uw_macaroon_encoding_decode_byte_str_(const uint8_t* cbor, + size_t cbor_len, const uint8_t** str, size_t* str_len); -bool uw_macaroon_encoding_decode_text_str_(const uint8_t* cbor, size_t cbor_len, +bool uw_macaroon_encoding_decode_text_str_(const uint8_t* cbor, + size_t cbor_len, const uint8_t** str, size_t* str_len); -- cgit v1.2.3 From a821f2ec61873d1ad9eb207d7b760b3aaf21248e Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 11:44:02 -0800 Subject: Integrate new macaroon library Implement validation of auth tokens (no session id check yet). BUG: 26292014 Change-Id: I55c9c8249f6355132486b2be8628c3538d504c5d Reviewed-on: https://weave-review.googlesource.com/2375 Reviewed-by: Alex Vakulenko --- src/privet/auth_manager.cc | 322 +++++++++++++++++++++----------- src/privet/auth_manager.h | 9 +- src/privet/auth_manager_unittest.cc | 46 +++-- src/privet/openssl_utils.cc | 10 +- third_party/libuweave/src/crypto_hmac.c | 51 ++--- 5 files changed, 273 insertions(+), 165 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 66d04c4..fd15c21 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -18,6 +18,7 @@ extern "C" { #include "third_party/libuweave/src/macaroon.h" +#include "third_party/libuweave/src/macaroon_caveat_internal.h" } namespace weave { @@ -25,10 +26,19 @@ namespace privet { namespace { +const time_t kJ2000ToTimeT = 946684800; const size_t kMaxMacaroonSize = 1024; const size_t kMaxPendingClaims = 10; const char kInvalidTokenError[] = "invalid_token"; +uint32_t ToJ2000Time(const base::Time& time) { + return std::max(time.ToTimeT(), kJ2000ToTimeT) - kJ2000ToTimeT; +} + +base::Time FromJ2000Time(uint32_t time) { + return base::Time::FromTimeT(time + kJ2000ToTimeT); +} + template void AppendToArray(T value, std::vector* array) { auto begin = reinterpret_cast(&value); @@ -37,78 +47,86 @@ void AppendToArray(T value, std::vector* array) { class Caveat { public: - // TODO(vitalybuka): Use _get_buffer_size_ when available. - Caveat(UwMacaroonCaveatType type, uint32_t value) : buffer(8) { - CHECK(uw_macaroon_caveat_create_with_uint_(type, value, buffer.data(), - buffer.size(), &caveat)); - } - - // TODO(vitalybuka): Use _get_buffer_size_ when available. - Caveat(UwMacaroonCaveatType type, const std::string& value) - : buffer(std::max(value.size(), 32u) * 2) { - CHECK(uw_macaroon_caveat_create_with_str_( - type, reinterpret_cast(value.data()), value.size(), - buffer.data(), buffer.size(), &caveat)); + Caveat(UwMacaroonCaveatType type, size_t str_len) + : buffer_(uw_macaroon_caveat_creation_get_buffsize_(type, str_len)) { + CHECK(!buffer_.empty()); } + const UwMacaroonCaveat& GetCaveat() const { return caveat_; } - const UwMacaroonCaveat& GetCaveat() const { return caveat; } - - private: - UwMacaroonCaveat caveat; - std::vector buffer; + protected: + UwMacaroonCaveat caveat_{}; + std::vector buffer_; DISALLOW_COPY_AND_ASSIGN(Caveat); }; -bool CheckCaveatType(const UwMacaroonCaveat& caveat, - UwMacaroonCaveatType type, - ErrorPtr* error) { - UwMacaroonCaveatType caveat_type{}; - if (!uw_macaroon_caveat_get_type_(&caveat, &caveat_type)) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unable to get type"); +class ScopeCaveat : public Caveat { + public: + explicit ScopeCaveat(UwMacaroonCaveatScopeType scope) + : Caveat(kUwMacaroonCaveatTypeScope, 0) { + CHECK(uw_macaroon_caveat_create_scope_(scope, buffer_.data(), + buffer_.size(), &caveat_)); } - if (caveat_type != type) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unexpected caveat type"); + DISALLOW_COPY_AND_ASSIGN(ScopeCaveat); +}; + +class TimestampCaveat : public Caveat { + public: + explicit TimestampCaveat(const base::Time& timestamp) + : Caveat(kUwMacaroonCaveatTypeDelegationTimestamp, 0) { + CHECK(uw_macaroon_caveat_create_delegation_timestamp_( + ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_)); } - return true; -} + DISALLOW_COPY_AND_ASSIGN(TimestampCaveat); +}; + +class ExpirationCaveat : public Caveat { + public: + explicit ExpirationCaveat(const base::Time& timestamp) + : Caveat(kUwMacaroonCaveatTypeExpirationAbsolute, 0) { + CHECK(uw_macaroon_caveat_create_expiration_absolute_( + ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_)); + } -bool ReadCaveat(const UwMacaroonCaveat& caveat, - UwMacaroonCaveatType type, - uint32_t* value, - ErrorPtr* error) { - if (!CheckCaveatType(caveat, type, error)) - return false; + DISALLOW_COPY_AND_ASSIGN(ExpirationCaveat); +}; - if (!uw_macaroon_caveat_get_value_uint_(&caveat, value)) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unable to read caveat"); +class UserIdCaveat : public Caveat { + public: + explicit UserIdCaveat(const std::string& user_id) + : Caveat(kUwMacaroonCaveatTypeDelegateeUser, user_id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_user_( + reinterpret_cast(user_id.data()), user_id.size(), + buffer_.data(), buffer_.size(), &caveat_)); } - return true; -} + DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); +}; -bool ReadCaveat(const UwMacaroonCaveat& caveat, - UwMacaroonCaveatType type, - std::string* value, - ErrorPtr* error) { - if (!CheckCaveatType(caveat, type, error)) - return false; +// class ServiceCaveat : public Caveat { +// public: +// ServiceCaveat() : Caveat(kUwMacaroonCaveatTypeDelegateeService, 0) { +// // TODO: Replace with service delegatee. +// CHECK(uw_macaroon_caveat_create_delegatee_user_( +// nullptr, 0, buffer_.data(), buffer_.size(), +// &caveat_)); +// } - const uint8_t* start{nullptr}; - size_t size{0}; - if (!uw_macaroon_caveat_get_value_str_(&caveat, &start, &size)) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unable to read caveat"); +// DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); +// }; + +class ClientAuthTokenCaveat : public Caveat { + public: + ClientAuthTokenCaveat() + : Caveat(kUwMacaroonCaveatTypeClientAuthorizationTokenV1, 0) { + CHECK(uw_macaroon_caveat_create_client_authorization_token_( + nullptr, 0, buffer_.data(), buffer_.size(), &caveat_)); } - value->assign(reinterpret_cast(start), size); - return true; -} + DISALLOW_COPY_AND_ASSIGN(ClientAuthTokenCaveat); +}; std::vector CreateSecret() { std::vector secret(kSha256OutputSize); @@ -122,18 +140,53 @@ bool IsClaimAllowed(RootClientTokenOwner curret, RootClientTokenOwner claimer) { std::vector CreateMacaroonToken( const std::vector& secret, - const std::vector& caveats) { + const base::Time& time, + const std::vector& caveats) { CHECK_EQ(kSha256OutputSize, secret.size()); + + UwMacaroonContext context{}; + CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context)); + UwMacaroon macaroon{}; - CHECK(uw_macaroon_new_from_root_key_(&macaroon, secret.data(), secret.size(), - caveats.data(), caveats.size())); + CHECK(uw_macaroon_create_from_root_key_(&macaroon, secret.data(), + secret.size(), &context, + caveats.data(), caveats.size())); + + std::vector serialized_token(kMaxMacaroonSize); + size_t len = 0; + CHECK(uw_macaroon_serialize_(&macaroon, serialized_token.data(), + serialized_token.size(), &len)); + serialized_token.resize(len); + + return serialized_token; +} - std::vector token(kMaxMacaroonSize); +std::vector ExtendMacaroonToken( + const UwMacaroon& macaroon, + const base::Time& time, + const std::vector& caveats) { + UwMacaroonContext context{}; + CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context)); + + UwMacaroon prev_macaroon = macaroon; + std::vector prev_buffer(kMaxMacaroonSize); + std::vector new_buffer(kMaxMacaroonSize); + + for (auto caveat : caveats) { + UwMacaroon new_macaroon{}; + CHECK(uw_macaroon_extend_(&prev_macaroon, &new_macaroon, &context, caveat, + new_buffer.data(), new_buffer.size())); + new_buffer.swap(prev_buffer); + prev_macaroon = new_macaroon; + } + + std::vector serialized_token(kMaxMacaroonSize); size_t len = 0; - CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len)); - token.resize(len); + CHECK(uw_macaroon_serialize_(&prev_macaroon, serialized_token.data(), + serialized_token.size(), &len)); + serialized_token.resize(len); - return token; + return serialized_token; } bool LoadMacaroon(const std::vector& token, @@ -141,8 +194,8 @@ bool LoadMacaroon(const std::vector& token, UwMacaroon* macaroon, ErrorPtr* error) { buffer->resize(kMaxMacaroonSize); - if (!uw_macaroon_load_(token.data(), token.size(), buffer->data(), - buffer->size(), macaroon)) { + if (!uw_macaroon_deserialize_(token.data(), token.size(), buffer->data(), + buffer->size(), macaroon)) { return Error::AddTo(error, FROM_HERE, kInvalidTokenError, "Invalid token format"); } @@ -151,10 +204,16 @@ bool LoadMacaroon(const std::vector& token, bool VerifyMacaroon(const std::vector& secret, const UwMacaroon& macaroon, + const base::Time& time, + UwMacaroonValidationResult* result, ErrorPtr* error) { CHECK_EQ(kSha256OutputSize, secret.size()); - if (!uw_macaroon_verify_(&macaroon, secret.data(), secret.size())) { - return Error::AddTo(error, FROM_HERE, "invalid_signature", + UwMacaroonContext context = {}; + CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context)); + + if (!uw_macaroon_validate_(&macaroon, secret.data(), secret.size(), &context, + result)) { + return Error::AddTo(error, FROM_HERE, "invalid_token", "Invalid token signature"); } return true; @@ -239,15 +298,16 @@ AuthManager::~AuthManager() {} std::vector AuthManager::CreateAccessToken(const UserInfo& user_info, base::TimeDelta ttl) const { - Caveat scope{kUwMacaroonCaveatTypeScope, ToMacaroonScope(user_info.scope())}; - Caveat user{kUwMacaroonCaveatTypeIdentifier, user_info.user_id()}; - Caveat issued{kUwMacaroonCaveatTypeExpiration, - static_cast((Now() + ttl).ToTimeT())}; + ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; + UserIdCaveat user{user_info.user_id()}; + const base::Time now = Now(); + ExpirationCaveat expiration{now + ttl}; return CreateMacaroonToken( - access_secret_, + access_secret_, now, { - scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(), + &scope.GetCaveat(), &user.GetCaveat(), &expiration.GetCaveat(), }); + return {}; } bool AuthManager::ParseAccessToken(const std::vector& token, @@ -256,35 +316,28 @@ bool AuthManager::ParseAccessToken(const std::vector& token, std::vector buffer; UwMacaroon macaroon{}; - uint32_t scope{0}; - std::string user_id; - uint32_t expiration{0}; - + UwMacaroonValidationResult result{}; + const base::Time now = Now(); if (!LoadMacaroon(token, &buffer, &macaroon, error) || - !VerifyMacaroon(access_secret_, macaroon, error) || macaroon.num_caveats != 3 || - !ReadCaveat(macaroon.caveats[0], kUwMacaroonCaveatTypeScope, &scope, - error) || - !ReadCaveat(macaroon.caveats[1], kUwMacaroonCaveatTypeIdentifier, - &user_id, error) || - !ReadCaveat(macaroon.caveats[2], kUwMacaroonCaveatTypeExpiration, - &expiration, error)) { + !VerifyMacaroon(access_secret_, macaroon, now, &result, error)) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, "Invalid token"); } - AuthScope auth_scope{FromMacaroonScope(scope)}; + AuthScope auth_scope{FromMacaroonScope(result.granted_scope)}; if (auth_scope == AuthScope::kNone) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, "Invalid token data"); } - base::Time time{base::Time::FromTimeT(expiration)}; - if (time < clock_->Now()) { - return Error::AddTo(error, FROM_HERE, errors::kAuthorizationExpired, - "Token is expired"); - } - + // If token is valid and token was not extended, it should has precisely this + // values. + CHECK_GE(FromJ2000Time(result.expiration_time), now); + CHECK_EQ(1u, result.num_delegatees); + CHECK(!result.delegatees[0].is_app); + std::string user_id{reinterpret_cast(result.delegatees[0].id), + result.delegatees[0].id_len}; if (user_info) *user_info = UserInfo{auth_scope, user_id}; @@ -309,7 +362,7 @@ std::vector AuthManager::ClaimRootClientAuthToken( std::unique_ptr{new AuthManager{nullptr, {}}}, owner)); if (pending_claims_.size() > kMaxPendingClaims) pending_claims_.pop_front(); - return pending_claims_.back().first->GetRootClientAuthToken(); + return pending_claims_.back().first->GetRootClientAuthToken(owner); } bool AuthManager::ConfirmClientAuthToken(const std::vector& token, @@ -332,14 +385,22 @@ bool AuthManager::ConfirmClientAuthToken(const std::vector& token, return true; } -std::vector AuthManager::GetRootClientAuthToken() const { - Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner}; - Caveat issued{kUwMacaroonCaveatTypeIssued, - static_cast(Now().ToTimeT())}; - return CreateMacaroonToken(auth_secret_, - { - scope.GetCaveat(), issued.GetCaveat(), - }); +std::vector AuthManager::GetRootClientAuthToken( + RootClientTokenOwner owner) const { + CHECK(RootClientTokenOwner::kNone != owner); + ClientAuthTokenCaveat auth_token; + const base::Time now = Now(); + TimestampCaveat issued{now}; + + UserIdCaveat client{""}; + // TODO: service caveat when available. + // ServiceCaveat cloud; + + return CreateMacaroonToken( + auth_secret_, now, + { + &auth_token.GetCaveat(), &issued.GetCaveat(), &client.GetCaveat(), + }); } base::Time AuthManager::Now() const { @@ -350,8 +411,9 @@ bool AuthManager::IsValidAuthToken(const std::vector& token, ErrorPtr* error) const { std::vector buffer; UwMacaroon macaroon{}; + UwMacaroonValidationResult result{}; if (!LoadMacaroon(token, &buffer, &macaroon, error) || - !VerifyMacaroon(auth_secret_, macaroon, error)) { + !VerifyMacaroon(auth_secret_, macaroon, Now(), &result, error)) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, "Invalid token"); } @@ -365,19 +427,49 @@ bool AuthManager::CreateAccessTokenFromAuth( AuthScope* access_token_scope, base::TimeDelta* access_token_ttl, ErrorPtr* error) const { - // TODO(vitalybuka): implement token validation. - if (!IsValidAuthToken(auth_token, error)) - return false; + std::vector buffer; + UwMacaroon macaroon{}; + UwMacaroonValidationResult result{}; + const base::Time now = Now(); + if (!LoadMacaroon(auth_token, &buffer, &macaroon, error) || + !VerifyMacaroon(auth_secret_, macaroon, now, &result, error)) { + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, + "Invalid token"); + } + + AuthScope auth_scope{FromMacaroonScope(result.granted_scope)}; + if (auth_scope == AuthScope::kNone) { + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, + "Invalid token data"); + } + + // TODO: Integrate black list checks. + // TODO: Check session id. + auto delegates_rbegin = std::reverse_iterator( + result.delegatees + result.num_delegatees); + auto delegates_rend = + std::reverse_iterator(result.delegatees); + auto last_user_id = + std::find_if(delegates_rbegin, delegates_rend, + [](const UwMacaroonDelegateeInfo& delegatee) { + return !delegatee.is_app; + }); + + if (last_user_id == delegates_rend || !last_user_id->id_len) { + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, + "User ID is missing"); + } + + CHECK_GE(FromJ2000Time(result.expiration_time), now); if (!access_token) return true; - // TODO(vitalybuka): User and scope must be parsed from auth_token. - UserInfo info{config_ ? config_->GetSettings().local_anonymous_access_role - : AuthScope::kViewer, - base::GenerateGUID()}; + std::string user_id{reinterpret_cast(last_user_id->id), + last_user_id->id_len}; + UserInfo info{auth_scope, user_id}; - // TODO(vitalybuka): TTL also should be reduced in accordance with auth_token. + ttl = std::min(ttl, FromJ2000Time(result.expiration_time) - now); *access_token = CreateAccessToken(info, ttl); if (access_token_scope) @@ -395,5 +487,21 @@ std::vector AuthManager::CreateSessionId() { return result; } +std::vector AuthManager::DelegateToUser( + const std::vector& token, + const UserInfo& user_info) const { + std::vector buffer; + UwMacaroon macaroon{}; + CHECK(LoadMacaroon(token, &buffer, &macaroon, nullptr)); + + ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; + UserIdCaveat user_caveat{user_info.user_id()}; + + return ExtendMacaroonToken(macaroon, Now(), + { + &scope.GetCaveat(), &user_caveat.GetCaveat(), + }); +} + } // namespace privet } // namespace weave diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h index 309d80e..0fa90a7 100644 --- a/src/privet/auth_manager.h +++ b/src/privet/auth_manager.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -54,7 +55,7 @@ class AuthManager { bool ConfirmClientAuthToken(const std::vector& token, ErrorPtr* error); - std::vector GetRootClientAuthToken() const; + std::vector GetRootClientAuthToken(RootClientTokenOwner owner) const; bool IsValidAuthToken(const std::vector& token, ErrorPtr* error) const; bool CreateAccessTokenFromAuth(const std::vector& auth_token, @@ -70,6 +71,12 @@ class AuthManager { std::vector CreateSessionId(); private: + FRIEND_TEST_ALL_PREFIXES(AuthManagerClaimTest, CreateAccessTokenFromAuth); + + // Test helper. + std::vector DelegateToUser(const std::vector& token, + const UserInfo& user_info) const; + Config* config_{nullptr}; // Can be nullptr for tests. base::DefaultClock default_clock_; base::Clock* clock_{&default_clock_}; diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index 70750ad..c5fba3c 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -64,18 +64,18 @@ TEST_F(AuthManagerTest, Constructor) { } TEST_F(AuthManagerTest, CreateAccessToken) { - EXPECT_EQ("UABRUHgcSZDry0bvIsoJv+WDQgEURQJjMjM0RgUaVArkgA==", + EXPECT_EQ("WCKDQgEURQlDMjM0RgUaG52hAFA3hFh7TexW1jC96sU4CxvN", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kViewer, "234"}, {}))); - EXPECT_EQ("UL7YEruLg5QQRDIp2+u1cqCDQgEIRQJjMjU3RgUaVArkgA==", + EXPECT_EQ("WCKDQgEIRQlDMjU3RgUaG52hAFD3dEHl3Y9Y28uoUESiYuLq", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kManager, "257"}, {}))); - EXPECT_EQ("UPFGeZRanR1wLGYLP5ZDkXiDQgECRQJjNDU2RgUaVArkgA==", + EXPECT_EQ("WCKDQgECRQlDNDU2RgUaG52hAFBy35bQdtvlqYf+Y/ANyxLU", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kOwner, "456"}, {}))); auto new_time = clock_.Now() + base::TimeDelta::FromDays(11); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("UMm9KlF3OEtZFBmhScJpl4uDQgEORQJjMzQ1RgUaVBllAA==", + EXPECT_EQ("WCKDQgEORQlDMzQ1RgUaG6whgFD1HGVxL8+FPaf/U0bOkXr8", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kUser, "345"}, {}))); } @@ -137,30 +137,40 @@ TEST_F(AuthManagerTest, ParseAccessToken) { } TEST_F(AuthManagerTest, GetRootClientAuthToken) { - EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==", - Base64Encode(auth_.GetRootClientAuthToken())); + EXPECT_EQ("WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==", + Base64Encode( + auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); +} + +TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentOwner) { + EXPECT_EQ( + "WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==", + Base64Encode(auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud))); } TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) { auto new_time = clock_.Now() + base::TimeDelta::FromDays(15); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("UBpNF8g/GbNUmAyHg1qqJr+CQgECRgMaVB6rAA==", - Base64Encode(auth_.GetRootClientAuthToken())); + EXPECT_EQ("WCCDQxkgAUYIGhuxZ4BCCUBQmNBWA9KdLzxHUCMqzonDZw==", + Base64Encode( + auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) { AuthManager auth{kSecret2, {}, kSecret1, &clock_}; - EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==", - Base64Encode(auth.GetRootClientAuthToken())); + EXPECT_EQ( + "WCCDQxkgAUYIGhudoQBCCUBQQ/BSJs7FEI260RnwjlJrVw==", + Base64Encode(auth.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } TEST_F(AuthManagerTest, IsValidAuthToken) { - EXPECT_TRUE(auth_.IsValidAuthToken(auth_.GetRootClientAuthToken(), nullptr)); + EXPECT_TRUE(auth_.IsValidAuthToken( + auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient), nullptr)); // Multiple attempts with random secrets. for (size_t i = 0; i < 1000; ++i) { AuthManager auth{{}, {}, {}, &clock_}; - auto token = auth.GetRootClientAuthToken(); + auto token = auth.GetRootClientAuthToken(RootClientTokenOwner::kClient); EXPECT_FALSE(auth_.IsValidAuthToken(token, nullptr)); EXPECT_TRUE(auth.IsValidAuthToken(token, nullptr)); } @@ -245,13 +255,17 @@ TEST_F(AuthManagerClaimTest, CreateAccessTokenFromAuth) { std::vector access_token; AuthScope scope; base::TimeDelta ttl; - EXPECT_TRUE(auth_.CreateAccessTokenFromAuth( - auth_.GetRootClientAuthToken(), base::TimeDelta::FromDays(1), - &access_token, &scope, &ttl, nullptr)); + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto extended = auth_.DelegateToUser(root, UserInfo{AuthScope::kUser, "234"}); + EXPECT_TRUE( + auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), + &access_token, &scope, &ttl, nullptr)); UserInfo user_info; EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr)); EXPECT_EQ(scope, user_info.scope()); - EXPECT_FALSE(user_info.user_id().empty()); + EXPECT_EQ(AuthScope::kUser, user_info.scope()); + + EXPECT_EQ("234", user_info.user_id()); } } // namespace privet diff --git a/src/privet/openssl_utils.cc b/src/privet/openssl_utils.cc index f38fd1a..17ebf70 100644 --- a/src/privet/openssl_utils.cc +++ b/src/privet/openssl_utils.cc @@ -18,13 +18,9 @@ namespace privet { std::vector HmacSha256(const std::vector& key, const std::vector& data) { std::vector mac(kSha256OutputSize); - uint8_t hmac_state[uw_crypto_hmac_required_buffer_size_()]; - CHECK(uw_crypto_hmac_init_(hmac_state, sizeof(hmac_state), key.data(), - key.size())); - CHECK(uw_crypto_hmac_update_(hmac_state, sizeof(hmac_state), data.data(), - data.size())); - CHECK(uw_crypto_hmac_final_(hmac_state, sizeof(hmac_state), mac.data(), - mac.size())); + const UwCryptoHmacMsg messages[] = {{data.data(), data.size()}}; + CHECK(uw_crypto_hmac_(key.data(), key.size(), messages, arraysize(messages), + mac.data(), mac.size())); return mac; } diff --git a/third_party/libuweave/src/crypto_hmac.c b/third_party/libuweave/src/crypto_hmac.c index 8b75133..d3dca65 100644 --- a/third_party/libuweave/src/crypto_hmac.c +++ b/third_party/libuweave/src/crypto_hmac.c @@ -11,41 +11,24 @@ #include #include -size_t uw_crypto_hmac_required_buffer_size_() { - return sizeof(HMAC_CTX); -} - -bool uw_crypto_hmac_init_(uint8_t* state_buffer, - size_t state_buffer_len, - const uint8_t* key, - size_t key_len) { - if (sizeof(HMAC_CTX) > state_buffer_len) { +bool uw_crypto_hmac_(const uint8_t* key, + size_t key_len, + const UwCryptoHmacMsg messages[], + size_t num_messages, + uint8_t* truncated_digest, + size_t truncated_digest_len) { + HMAC_CTX context = {0}; + HMAC_CTX_init(&context); + if (!HMAC_Init(&context, key, key_len, EVP_sha256())) return false; - } - HMAC_CTX* context = (HMAC_CTX*)state_buffer; - HMAC_CTX_init(context); - return HMAC_Init(context, key, key_len, EVP_sha256()); -} -bool uw_crypto_hmac_update_(uint8_t* state_buffer, - size_t state_buffer_len, - const uint8_t* data, - size_t data_len) { - if (sizeof(HMAC_CTX) > state_buffer_len) { - return false; - } - HMAC_CTX* context = (HMAC_CTX*)state_buffer; - return HMAC_Update(context, data, data_len); -} - -bool uw_crypto_hmac_final_(uint8_t* state_buffer, - size_t state_buffer_len, - uint8_t* truncated_digest, - size_t truncated_digest_len) { - if (sizeof(HMAC_CTX) > state_buffer_len) { - return false; + for (size_t i = 0; i < num_messages; ++i) { + if (messages[i].num_bytes && + (!messages[i].bytes || + !HMAC_Update(&context, messages[i].bytes, messages[i].num_bytes))) { + return false; + } } - HMAC_CTX* context = (HMAC_CTX*)state_buffer; const size_t kFullDigestLen = (size_t)EVP_MD_size(EVP_sha256()); if (truncated_digest_len > kFullDigestLen) { @@ -55,8 +38,8 @@ bool uw_crypto_hmac_final_(uint8_t* state_buffer, uint8_t digest[kFullDigestLen]; uint32_t len = kFullDigestLen; - bool result = HMAC_Final(context, digest, &len) && kFullDigestLen == len; - HMAC_CTX_cleanup(context); + bool result = HMAC_Final(&context, digest, &len) && kFullDigestLen == len; + HMAC_CTX_cleanup(&context); if (result) { memcpy(truncated_digest, digest, truncated_digest_len); } -- cgit v1.2.3 From d74a732bfae910b08d6d0f83a86cde04c3aa2cd5 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 18:39:36 -0800 Subject: Update macaroon lib Change-Id: I37472f11827b8bf1af28c6f525df85093f5f3a56 Reviewed-on: https://weave-review.googlesource.com/2381 Reviewed-by: Alex Vakulenko --- third_party/libuweave/src/macaroon.c | 2 +- third_party/libuweave/src/macaroon.h | 9 +++++- third_party/libuweave/src/macaroon_caveat.c | 48 +++++++++++++++++++++++------ third_party/libuweave/src/macaroon_caveat.h | 24 ++++++++++----- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/third_party/libuweave/src/macaroon.c b/third_party/libuweave/src/macaroon.c index 50b2d00..aa775c2 100644 --- a/third_party/libuweave/src/macaroon.c +++ b/third_party/libuweave/src/macaroon.c @@ -146,7 +146,7 @@ static void reset_validation_result(UwMacaroonValidationResult* result) { for (size_t i = 0; i < MAX_NUM_DELEGATEES; i++) { result->delegatees[i].id = NULL; result->delegatees[i].id_len = 0; - result->delegatees[i].is_app = true; + result->delegatees[i].type = kUwMacaroonDelegateeTypeNone; } } diff --git a/third_party/libuweave/src/macaroon.h b/third_party/libuweave/src/macaroon.h index dfaaba7..c93bbb2 100644 --- a/third_party/libuweave/src/macaroon.h +++ b/third_party/libuweave/src/macaroon.h @@ -25,10 +25,17 @@ typedef struct { } UwMacaroon; // For the delegatee list in the validation result object +typedef enum { + kUwMacaroonDelegateeTypeNone = 0, + kUwMacaroonDelegateeTypeUser = 1, + kUwMacaroonDelegateeTypeApp = 2, + kUwMacaroonDelegateeTypeService = 3, +} UwMacaroonDelegateeType; + typedef struct { const uint8_t* id; size_t id_len; - bool is_app; + UwMacaroonDelegateeType type; } UwMacaroonDelegateeInfo; #define MAX_NUM_DELEGATEES 10 diff --git a/third_party/libuweave/src/macaroon_caveat.c b/third_party/libuweave/src/macaroon_caveat.c index b8b2183..a2b26dc 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -12,8 +12,6 @@ #include "src/macaroon_context.h" #include "src/macaroon_encoding.h" -#define MAX_CBOR_STR_LEN_FOR_UINT 5 - static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { switch (type) { case kUwMacaroonCaveatTypeNonce: @@ -25,6 +23,7 @@ static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: case kUwMacaroonCaveatTypeAppCommandsOnly: + case kUwMacaroonCaveatTypeDelegateeService: case kUwMacaroonCaveatTypeBleSessionID: case kUwMacaroonCaveatTypeLanSessionID: case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: @@ -103,7 +102,7 @@ static bool create_caveat_bstr_value_(UwMacaroonCaveatType type, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { - if ((str == NULL && str_len != 0) || buffer == NULL || buffer_size == 0 || + if (str == NULL || str_len == 0 || buffer == NULL || buffer_size == 0 || new_caveat == NULL || uw_macaroon_caveat_creation_get_buffsize_(type, str_len) > buffer_size) { return false; @@ -147,6 +146,7 @@ size_t uw_macaroon_caveat_creation_get_buffsize_(UwMacaroonCaveatType type, case kUwMacaroonCaveatTypeNonce: case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: + case kUwMacaroonCaveatTypeDelegateeService: case kUwMacaroonCaveatTypeLanSessionID: case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: @@ -237,6 +237,17 @@ bool uw_macaroon_caveat_create_app_commands_only_( buffer_size, new_caveat); } +bool uw_macaroon_caveat_create_delegatee_service_( + const uint8_t* id_str, + size_t id_str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat) { + return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeService, + id_str, id_str_len, buffer, buffer_size, + new_caveat); +} + bool uw_macaroon_caveat_create_ble_session_id_(uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { @@ -393,12 +404,29 @@ static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, if (result->num_delegatees >= MAX_NUM_DELEGATEES) { return false; } - bool is_app = (caveat_type == kUwMacaroonCaveatTypeDelegateeApp); - if (is_app) { + UwMacaroonDelegateeType delegatee_type = kUwMacaroonDelegateeTypeNone; + switch (caveat_type) { + case kUwMacaroonCaveatTypeDelegateeUser: + delegatee_type = kUwMacaroonDelegateeTypeUser; + break; + + case kUwMacaroonCaveatTypeDelegateeApp: + delegatee_type = kUwMacaroonDelegateeTypeApp; + break; + + case kUwMacaroonCaveatTypeDelegateeService: + delegatee_type = kUwMacaroonDelegateeTypeService; + break; + + default: + return false; + } + + if (caveat_type != kUwMacaroonCaveatTypeDelegateeUser) { for (size_t i = 0; i < result->num_delegatees; i++) { - // There must have at most one DelegateeApp - if (result->delegatees[i].is_app) { + // There must have at most one DelegateeApp or DelegateeService + if (result->delegatees[i].type == delegatee_type) { return false; } } @@ -409,7 +437,7 @@ static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, &(result->delegatees[result->num_delegatees].id_len))) { return false; } - result->delegatees[result->num_delegatees].is_app = is_app; + result->delegatees[result->num_delegatees].type = delegatee_type; result->num_delegatees++; return true; } @@ -463,9 +491,8 @@ bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, // Need to create a list of delegatees case kUwMacaroonCaveatTypeDelegateeUser: - return update_delegatee_list(caveat_type, caveat, result); - case kUwMacaroonCaveatTypeDelegateeApp: + case kUwMacaroonCaveatTypeDelegateeService: return update_delegatee_list(caveat_type, caveat, result); // Time related caveats @@ -543,6 +570,7 @@ bool uw_macaroon_caveat_get_value_bstr_(const UwMacaroonCaveat* caveat, if (type != kUwMacaroonCaveatTypeNonce && type != kUwMacaroonCaveatTypeDelegateeUser && type != kUwMacaroonCaveatTypeDelegateeApp && + type != kUwMacaroonCaveatTypeDelegateeService && type != kUwMacaroonCaveatTypeLanSessionID && type != kUwMacaroonCaveatTypeClientAuthorizationTokenV1 && type != kUwMacaroonCaveatTypeServerAuthenticationTokenV1) { diff --git a/third_party/libuweave/src/macaroon_caveat.h b/third_party/libuweave/src/macaroon_caveat.h index 9baf70d..b6846e8 100644 --- a/third_party/libuweave/src/macaroon_caveat.h +++ b/third_party/libuweave/src/macaroon_caveat.h @@ -15,14 +15,17 @@ typedef struct { } UwMacaroonCaveat; typedef enum { - kUwMacaroonCaveatTypeNonce = 0, // bstr - kUwMacaroonCaveatTypeScope = 1, // uint - kUwMacaroonCaveatTypeExpirationAbsolute = 5, // uint - kUwMacaroonCaveatTypeTTL1Hour = 6, // no value - kUwMacaroonCaveatTypeTTL24Hour = 7, // no value - kUwMacaroonCaveatTypeDelegationTimestamp = 8, // uint - kUwMacaroonCaveatTypeDelegateeUser = 9, // bstr - kUwMacaroonCaveatTypeDelegateeApp = 10, // bstr + kUwMacaroonCaveatTypeNonce = 0, // bstr + kUwMacaroonCaveatTypeScope = 1, // uint + kUwMacaroonCaveatTypeExpirationAbsolute = 5, // uint + kUwMacaroonCaveatTypeTTL1Hour = 6, // no value + kUwMacaroonCaveatTypeTTL24Hour = 7, // no value + kUwMacaroonCaveatTypeDelegationTimestamp = 8, // uint + + kUwMacaroonCaveatTypeDelegateeUser = 9, // bstr + kUwMacaroonCaveatTypeDelegateeApp = 10, // bstr + kUwMacaroonCaveatTypeDelegateeService = 12, // bstr + kUwMacaroonCaveatTypeAppCommandsOnly = 11, // no value kUwMacaroonCaveatTypeBleSessionID = 16, // no value kUwMacaroonCaveatTypeLanSessionID = 17, // bstr @@ -81,6 +84,11 @@ bool uw_macaroon_caveat_create_delegatee_app_(const uint8_t* id_str, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat); +bool uw_macaroon_caveat_create_delegatee_service_(const uint8_t* id_str, + size_t id_str_len, + uint8_t* buffer, + size_t buffer_size, + UwMacaroonCaveat* new_caveat); bool uw_macaroon_caveat_create_app_commands_only_(uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat); -- cgit v1.2.3 From d7c6deb0576805c0e043686e220a7a27e17b50d4 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 15:59:28 -0800 Subject: Remove crypto type "None" Unused for a while. If necessary better to set local_anonymous_access_role into kOwner. Change-Id: Ifdd39a9a6069f54ac641730550ed71da106fe10e Reviewed-on: https://weave-review.googlesource.com/2377 Reviewed-by: Alex Vakulenko --- examples/daemon/common/daemon.h | 7 +------ examples/provider/file_config_store.cc | 7 ++----- examples/provider/file_config_store.h | 4 +--- include/weave/settings.h | 1 - src/config_unittest.cc | 3 --- src/privet/privet_manager.cc | 5 ----- src/privet/privet_manager.h | 1 - src/privet/privet_types.cc | 1 - src/privet/privet_types.h | 1 - src/privet/security_manager.cc | 31 ------------------------------- 10 files changed, 4 insertions(+), 57 deletions(-) diff --git a/examples/daemon/common/daemon.h b/examples/daemon/common/daemon.h index 6dc021d..985c5e5 100644 --- a/examples/daemon/common/daemon.h +++ b/examples/daemon/common/daemon.h @@ -20,7 +20,6 @@ class Daemon { public: struct Options { bool force_bootstrapping_{false}; - bool disable_security_{false}; bool disable_privet_{false}; std::string registration_ticket_; std::string model_id_{"AAAAA"}; @@ -31,7 +30,6 @@ class Daemon { << "\t-h,--help Show this help message\n" << "\t--v=LEVEL Logging level\n" << "\t-b,--bootstrapping Force WiFi bootstrapping\n" - << "\t-d,--disable_security Disable privet security\n" << "\t--registration_ticket=TICKET Register device with the " "given ticket\n" << "\t--disable_privet Disable local privet\n"; @@ -44,8 +42,6 @@ class Daemon { return false; } else if (arg == "-b" || arg == "--bootstrapping") { force_bootstrapping_ = true; - } else if (arg == "-d" || arg == "--disable_security") { - disable_security_ = true; } else if (arg == "--disable_privet") { disable_privet_ = true; } else if (arg.find("--registration_ticket") != std::string::npos) { @@ -71,8 +67,7 @@ class Daemon { Daemon(const Options& opts) : task_runner_{new weave::examples::EventTaskRunner}, config_store_{ - new weave::examples::FileConfigStore(opts.disable_security_, - opts.model_id_, + new weave::examples::FileConfigStore(opts.model_id_, task_runner_.get())}, http_client_{new weave::examples::CurlHttpClient(task_runner_.get())}, network_{new weave::examples::EventNetworkImpl(task_runner_.get())}, diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc index 31efaa7..a6c2e60 100644 --- a/examples/provider/file_config_store.cc +++ b/examples/provider/file_config_store.cc @@ -19,11 +19,9 @@ namespace examples { const char kSettingsDir[] = "/var/lib/weave/"; -FileConfigStore::FileConfigStore(bool disable_security, - const std::string& model_id, +FileConfigStore::FileConfigStore(const std::string& model_id, provider::TaskRunner* task_runner) - : disable_security_{disable_security}, - model_id_{model_id}, + : model_id_{model_id}, task_runner_{task_runner} {} std::string FileConfigStore::GetPath(const std::string& name) const { @@ -61,7 +59,6 @@ bool FileConfigStore::LoadDefaults(Settings* settings) { settings->client_secret = "LS_iPYo_WIOE0m2VnLdduhnx"; settings->api_key = "AIzaSyACK3oZtmIylUKXiTMqkZqfuRiCgQmQSAQ"; - settings->disable_security = disable_security_; return true; } diff --git a/examples/provider/file_config_store.h b/examples/provider/file_config_store.h index e7398d1..337e82a 100644 --- a/examples/provider/file_config_store.h +++ b/examples/provider/file_config_store.h @@ -17,8 +17,7 @@ namespace examples { class FileConfigStore : public provider::ConfigStore { public: - FileConfigStore(bool disable_security, - const std::string& model_id, + FileConfigStore(const std::string& model_id, provider::TaskRunner* task_runner); bool LoadDefaults(Settings* settings) override; @@ -31,7 +30,6 @@ class FileConfigStore : public provider::ConfigStore { private: std::string GetPath(const std::string& name) const; - const bool disable_security_; const std::string model_id_; provider::TaskRunner* task_runner_{nullptr}; }; diff --git a/include/weave/settings.h b/include/weave/settings.h index eeb3f93..741fff2 100644 --- a/include/weave/settings.h +++ b/include/weave/settings.h @@ -71,7 +71,6 @@ struct Settings { // Internal options to tweak some library functionality. External code should // avoid using them. bool wifi_auto_setup_enabled{true}; - bool disable_security{false}; std::string test_privet_ssid; }; diff --git a/src/config_unittest.cc b/src/config_unittest.cc index fbb558a..4b0e5b4 100644 --- a/src/config_unittest.cc +++ b/src/config_unittest.cc @@ -68,7 +68,6 @@ TEST_F(ConfigTest, Defaults) { EXPECT_FALSE(GetSettings().device_id.empty()); EXPECT_EQ("", GetSettings().firmware_version); EXPECT_TRUE(GetSettings().wifi_auto_setup_enabled); - EXPECT_FALSE(GetSettings().disable_security); EXPECT_EQ("", GetSettings().test_privet_ssid); EXPECT_EQ(std::set{PairingType::kPinCode}, GetSettings().pairing_modes); @@ -164,8 +163,6 @@ TEST_F(ConfigTest, LoadState) { EXPECT_EQ("state_device_id", GetSettings().device_id); EXPECT_EQ(GetDefaultSettings().wifi_auto_setup_enabled, GetSettings().wifi_auto_setup_enabled); - EXPECT_EQ(GetDefaultSettings().disable_security, - GetSettings().disable_security); EXPECT_EQ(GetDefaultSettings().test_privet_ssid, GetSettings().test_privet_ssid); EXPECT_EQ(GetDefaultSettings().pairing_modes, GetSettings().pairing_modes); diff --git a/src/privet/privet_manager.cc b/src/privet/privet_manager.cc index edc7907..9c717ce 100644 --- a/src/privet/privet_manager.cc +++ b/src/privet/privet_manager.cc @@ -53,8 +53,6 @@ void Manager::Start(Network* network, CHECK(auth_manager); CHECK(device); - disable_security_ = device->GetSettings().disable_security; - device_ = DeviceDelegate::CreateDefault( task_runner_, http_server->GetHttpPort(), http_server->GetHttpsPort(), http_server->GetRequestTimeout()); @@ -129,9 +127,6 @@ void Manager::PrivetRequestHandlerWithData( const std::shared_ptr& request, const std::string& data) { std::string auth_header = request->GetFirstHeader(http::kAuthorization); - if (auth_header.empty() && disable_security_) - auth_header = "Privet anonymous"; - base::DictionaryValue empty; auto value = base::JSONReader::Read(data); const base::DictionaryValue* dictionary = ∅ diff --git a/src/privet/privet_manager.h b/src/privet/privet_manager.h index 371d843..06eb89a 100644 --- a/src/privet/privet_manager.h +++ b/src/privet/privet_manager.h @@ -79,7 +79,6 @@ class Manager : public CloudDelegate::Observer { void OnChanged(); void OnConnectivityChanged(); - bool disable_security_{false}; provider::TaskRunner* task_runner_{nullptr}; std::unique_ptr cloud_; std::unique_ptr device_; diff --git a/src/privet/privet_types.cc b/src/privet/privet_types.cc index dd291b3..9e50f94 100644 --- a/src/privet/privet_types.cc +++ b/src/privet/privet_types.cc @@ -52,7 +52,6 @@ const EnumToStringMap::Map kWifiTypeMap[] = { }; const EnumToStringMap::Map kCryptoTypeMap[] = { - {CryptoType::kNone, "none"}, {CryptoType::kSpake_p224, "p224_spake2"}, }; diff --git a/src/privet/privet_types.h b/src/privet/privet_types.h index c738865..49c4522 100644 --- a/src/privet/privet_types.h +++ b/src/privet/privet_types.h @@ -15,7 +15,6 @@ namespace weave { namespace privet { enum class CryptoType { - kNone, kSpake_p224, }; diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc index 358876d..0f00699 100644 --- a/src/privet/security_manager.cc +++ b/src/privet/security_manager.cc @@ -67,25 +67,6 @@ class Spakep224Exchanger : public SecurityManager::KeyExchanger { crypto::P224EncryptedKeyExchange spake_; }; -class UnsecureKeyExchanger : public SecurityManager::KeyExchanger { - public: - explicit UnsecureKeyExchanger(const std::string& password) - : password_(password) {} - ~UnsecureKeyExchanger() override = default; - - // SecurityManager::KeyExchanger methods. - const std::string& GetMessage() override { return password_; } - - bool ProcessMessage(const std::string& message, ErrorPtr* error) override { - return true; - } - - const std::string& GetKey() const override { return password_; } - - private: - std::string password_; -}; - } // namespace SecurityManager::SecurityManager(const Config* config, @@ -218,8 +199,6 @@ std::set SecurityManager::GetPairingTypes() const { std::set SecurityManager::GetCryptoTypes() const { std::set result{CryptoType::kSpake_p224}; - if (GetSettings().disable_security) - result.insert(CryptoType::kNone); return result; } @@ -259,8 +238,6 @@ const Config::Settings& SecurityManager::GetSettings() const { bool SecurityManager::IsValidPairingCode( const std::vector& auth_code) const { - if (GetSettings().disable_security) - return true; for (const auto& session : confirmed_sessions_) { const std::string& key = session.second->GetKey(); const std::string& id = session.first; @@ -309,11 +286,6 @@ bool SecurityManager::StartPairing(PairingType mode, case CryptoType::kSpake_p224: spake.reset(new Spakep224Exchanger(code)); break; - case CryptoType::kNone: - if (GetSettings().disable_security) { - spake.reset(new UnsecureKeyExchanger(code)); - break; - } // Fall through... default: return Error::AddTo(error, FROM_HERE, errors::kInvalidParams, @@ -428,9 +400,6 @@ void SecurityManager::RegisterPairingListeners( } bool SecurityManager::CheckIfPairingAllowed(ErrorPtr* error) { - if (GetSettings().disable_security) - return true; - if (block_pairing_until_ > auth_manager_->Now()) { return Error::AddTo(error, FROM_HERE, errors::kDeviceBusy, "Too many pairing attempts"); -- cgit v1.2.3 From d5f7aabc2a7e414c63dc5a20a294c71a54111910 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 18:25:14 -0800 Subject: Add session ID validation BUG: 26292014 Change-Id: I2a71dbf3dbc4b422c8f9bedd806f459d2bc35333 Reviewed-on: https://weave-review.googlesource.com/2380 Reviewed-by: Alex Vakulenko --- src/privet/auth_manager.cc | 64 ++++++++++++++------ src/privet/auth_manager.h | 10 ++-- src/privet/auth_manager_unittest.cc | 116 ++++++++++++++++++++++++++++++------ src/privet/security_manager.cc | 2 +- 4 files changed, 153 insertions(+), 39 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index fd15c21..0a2b75a 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -30,6 +30,7 @@ const time_t kJ2000ToTimeT = 946684800; const size_t kMaxMacaroonSize = 1024; const size_t kMaxPendingClaims = 10; const char kInvalidTokenError[] = "invalid_token"; +const int kSessionIdTtlMinutes = 1; uint32_t ToJ2000Time(const base::Time& time) { return std::max(time.ToTimeT(), kJ2000ToTimeT) - kJ2000ToTimeT; @@ -95,11 +96,11 @@ class ExpirationCaveat : public Caveat { class UserIdCaveat : public Caveat { public: - explicit UserIdCaveat(const std::string& user_id) - : Caveat(kUwMacaroonCaveatTypeDelegateeUser, user_id.size()) { + explicit UserIdCaveat(const std::string& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeUser, id.size()) { CHECK(uw_macaroon_caveat_create_delegatee_user_( - reinterpret_cast(user_id.data()), user_id.size(), - buffer_.data(), buffer_.size(), &caveat_)); + reinterpret_cast(id.data()), id.size(), buffer_.data(), + buffer_.size(), &caveat_)); } DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); @@ -117,6 +118,18 @@ class UserIdCaveat : public Caveat { // DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); // }; +class SessionIdCaveat : public Caveat { + public: + explicit SessionIdCaveat(const std::string& id) + : Caveat(kUwMacaroonCaveatTypeLanSessionID, id.size()) { + CHECK(uw_macaroon_caveat_create_lan_session_id_( + reinterpret_cast(id.data()), id.size(), buffer_.data(), + buffer_.size(), &caveat_)); + } + + DISALLOW_COPY_AND_ASSIGN(SessionIdCaveat); +}; + class ClientAuthTokenCaveat : public Caveat { public: ClientAuthTokenCaveat() @@ -439,12 +452,11 @@ bool AuthManager::CreateAccessTokenFromAuth( AuthScope auth_scope{FromMacaroonScope(result.granted_scope)}; if (auth_scope == AuthScope::kNone) { - return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, "Invalid token data"); } // TODO: Integrate black list checks. - // TODO: Check session id. auto delegates_rbegin = std::reverse_iterator( result.delegatees + result.num_delegatees); auto delegates_rend = @@ -456,10 +468,16 @@ bool AuthManager::CreateAccessTokenFromAuth( }); if (last_user_id == delegates_rend || !last_user_id->id_len) { - return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, "User ID is missing"); } + const char* session_id = reinterpret_cast(result.lan_session_id); + if (!IsValidSessionId({session_id, session_id + result.lan_session_id_len})) { + return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, + "Invalid session id"); + } + CHECK_GE(FromJ2000Time(result.expiration_time), now); if (!access_token) @@ -480,27 +498,39 @@ bool AuthManager::CreateAccessTokenFromAuth( return true; } -std::vector AuthManager::CreateSessionId() { - std::vector result; - AppendToArray(Now().ToTimeT(), &result); - AppendToArray(++session_counter_, &result); - return result; +std::string AuthManager::CreateSessionId() const { + return std::to_string(ToJ2000Time(Now())) + ":" + + std::to_string(++session_counter_); +} + +bool AuthManager::IsValidSessionId(const std::string& session_id) const { + base::Time ssid_time = FromJ2000Time(std::atoi(session_id.c_str())); + return Now() - base::TimeDelta::FromMinutes(kSessionIdTtlMinutes) <= + ssid_time && + ssid_time <= Now(); } std::vector AuthManager::DelegateToUser( const std::vector& token, + base::TimeDelta ttl, const UserInfo& user_info) const { std::vector buffer; UwMacaroon macaroon{}; CHECK(LoadMacaroon(token, &buffer, &macaroon, nullptr)); + const base::Time now = Now(); + TimestampCaveat issued{now}; + ExpirationCaveat expiration{now + ttl}; ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; - UserIdCaveat user_caveat{user_info.user_id()}; + UserIdCaveat user{user_info.user_id()}; + SessionIdCaveat session{CreateSessionId()}; - return ExtendMacaroonToken(macaroon, Now(), - { - &scope.GetCaveat(), &user_caveat.GetCaveat(), - }); + return ExtendMacaroonToken( + macaroon, now, + { + &issued.GetCaveat(), &expiration.GetCaveat(), &scope.GetCaveat(), + &user.GetCaveat(), &session.GetCaveat(), + }); } } // namespace privet diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h index 0fa90a7..f0a5761 100644 --- a/src/privet/auth_manager.h +++ b/src/privet/auth_manager.h @@ -68,19 +68,21 @@ class AuthManager { void SetAuthSecret(const std::vector& secret, RootClientTokenOwner owner); - std::vector CreateSessionId(); + std::string CreateSessionId() const; + bool IsValidSessionId(const std::string& session_id) const; private: - FRIEND_TEST_ALL_PREFIXES(AuthManagerClaimTest, CreateAccessTokenFromAuth); + friend class AuthManagerTest; - // Test helper. + // Test helpers. Device does not need to implement delegation. std::vector DelegateToUser(const std::vector& token, + base::TimeDelta ttl, const UserInfo& user_info) const; Config* config_{nullptr}; // Can be nullptr for tests. base::DefaultClock default_clock_; base::Clock* clock_{&default_clock_}; - uint32_t session_counter_{0}; + mutable uint32_t session_counter_{0}; std::vector auth_secret_; // Persistent. std::vector certificate_fingerprint_; diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index c5fba3c..d74abd8 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -29,6 +29,11 @@ class AuthManagerTest : public testing::Test { } protected: + std::vector DelegateToUser(const std::vector& token, + base::TimeDelta ttl, + const UserInfo& user_info) const { + return auth_.DelegateToUser(token, ttl, user_info); + } const std::vector kSecret1{ 78, 40, 39, 68, 29, 19, 70, 86, 38, 61, 13, 55, 33, 32, 51, 52, 34, 43, 97, 48, 8, 56, 11, 99, 50, 59, 24, 26, 31, 71, 76, 28}; @@ -129,6 +134,10 @@ TEST_F(AuthManagerTest, ParseAccessToken) { .WillRepeatedly(Return(kStartTime + base::TimeDelta::FromSeconds(i))); EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr)); + auto extended = DelegateToUser(token, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, "234"}); + EXPECT_FALSE(auth.ParseAccessToken(extended, &user_info, nullptr)); + EXPECT_CALL(clock_, Now()) .WillRepeatedly( Return(kStartTime + base::TimeDelta::FromSeconds(i + 1))); @@ -176,6 +185,96 @@ TEST_F(AuthManagerTest, IsValidAuthToken) { } } +TEST_F(AuthManagerTest, CreateSessionId) { + EXPECT_EQ("463315200:1", auth_.CreateSessionId()); +} + +TEST_F(AuthManagerTest, IsValidSessionId) { + EXPECT_TRUE(auth_.IsValidSessionId("463315200:1")); + EXPECT_TRUE(auth_.IsValidSessionId("463315200:2")); + EXPECT_TRUE(auth_.IsValidSessionId("463315150")); + + // Future + EXPECT_FALSE(auth_.IsValidSessionId("463315230:1")); + + // Expired + EXPECT_FALSE(auth_.IsValidSessionId("463315100:1")); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuth) { + std::vector access_token; + AuthScope scope; + base::TimeDelta ttl; + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, "234"}); + EXPECT_EQ( + "WEWIQxkgAUYIGhudoQBCCUBGCBobnaEARgUaG52k6EIBDkUJQzIzNE0RSzQ2MzMxNTIwMDox" + "UHN8Lm+CUQo7s84Sh+grpAE=", + Base64Encode(extended)); + EXPECT_TRUE( + auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), + &access_token, &scope, &ttl, nullptr)); + UserInfo user_info; + EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr)); + EXPECT_EQ(scope, user_info.scope()); + EXPECT_EQ(AuthScope::kUser, user_info.scope()); + + EXPECT_EQ("234", user_info.user_id()); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuthNotMinted) { + std::vector access_token; + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + ErrorPtr error; + EXPECT_FALSE(auth_.CreateAccessTokenFromAuth( + root, base::TimeDelta::FromDays(1), nullptr, nullptr, nullptr, &error)); + EXPECT_TRUE(error->HasError("invalidAuthCode")); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuthValidateAfterSomeTime) { + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, "234"}); + + // new_time < session_id_expiration < token_expiration. + auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(15); + EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); + EXPECT_TRUE( + auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), + nullptr, nullptr, nullptr, nullptr)); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpired) { + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(10), + UserInfo{AuthScope::kUser, "234"}); + ErrorPtr error; + + // token_expiration < new_time < session_id_expiration. + auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(15); + EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); + EXPECT_FALSE( + auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), + nullptr, nullptr, nullptr, &error)); + EXPECT_TRUE(error->HasError("invalidAuthCode")); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpiredSessionid) { + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, "234"}); + ErrorPtr error; + + // session_id_expiration < new_time < token_expiration. + auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(200); + EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); + EXPECT_FALSE( + auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), + nullptr, nullptr, nullptr, &error)); + EXPECT_TRUE(error->HasError("invalidAuthCode")); +} + class AuthManagerClaimTest : public testing::Test { public: void SetUp() override { EXPECT_EQ(auth_.GetAuthSecret().size(), 32u); } @@ -251,22 +350,5 @@ TEST_F(AuthManagerClaimTest, TokenOverflow) { EXPECT_FALSE(auth_.ConfirmClientAuthToken(token, nullptr)); } -TEST_F(AuthManagerClaimTest, CreateAccessTokenFromAuth) { - std::vector access_token; - AuthScope scope; - base::TimeDelta ttl; - auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); - auto extended = auth_.DelegateToUser(root, UserInfo{AuthScope::kUser, "234"}); - EXPECT_TRUE( - auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), - &access_token, &scope, &ttl, nullptr)); - UserInfo user_info; - EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr)); - EXPECT_EQ(scope, user_info.scope()); - EXPECT_EQ(AuthScope::kUser, user_info.scope()); - - EXPECT_EQ("234", user_info.user_id()); -} - } // namespace privet } // namespace weave diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc index 0f00699..04164b3 100644 --- a/src/privet/security_manager.cc +++ b/src/privet/security_manager.cc @@ -388,7 +388,7 @@ bool SecurityManager::CancelPairing(const std::string& session_id, } std::string SecurityManager::CreateSessionId() { - return Base64Encode(auth_manager_->CreateSessionId()); + return auth_manager_->CreateSessionId(); } void SecurityManager::RegisterPairingListeners( -- cgit v1.2.3 From 70c8642747634743cecac1944b3fdfe746fd9e1a Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Wed, 27 Jan 2016 18:56:37 -0800 Subject: Add kUwMacaroonDelegateeTypeService caveat BUG: 26292014 Change-Id: I4edc9fea58265d8345010023c88208155772e6f9 Reviewed-on: https://weave-review.googlesource.com/2382 Reviewed-by: Alex Vakulenko --- src/privet/auth_manager.cc | 32 +++++++++++++++----------------- src/privet/auth_manager_unittest.cc | 14 +++++++------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 0a2b75a..71a4f39 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -106,17 +106,17 @@ class UserIdCaveat : public Caveat { DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); }; -// class ServiceCaveat : public Caveat { -// public: -// ServiceCaveat() : Caveat(kUwMacaroonCaveatTypeDelegateeService, 0) { -// // TODO: Replace with service delegatee. -// CHECK(uw_macaroon_caveat_create_delegatee_user_( -// nullptr, 0, buffer_.data(), buffer_.size(), -// &caveat_)); -// } - -// DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); -// }; +class ServiceCaveat : public Caveat { + public: + explicit ServiceCaveat(const std::string& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeService, id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_service_( + reinterpret_cast(id.data()), id.size(), buffer_.data(), + buffer_.size(), &caveat_)); + } + + DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); +}; class SessionIdCaveat : public Caveat { public: @@ -348,7 +348,7 @@ bool AuthManager::ParseAccessToken(const std::vector& token, // values. CHECK_GE(FromJ2000Time(result.expiration_time), now); CHECK_EQ(1u, result.num_delegatees); - CHECK(!result.delegatees[0].is_app); + CHECK_EQ(kUwMacaroonDelegateeTypeUser, result.delegatees[0].type); std::string user_id{reinterpret_cast(result.delegatees[0].id), result.delegatees[0].id_len}; if (user_info) @@ -405,10 +405,8 @@ std::vector AuthManager::GetRootClientAuthToken( const base::Time now = Now(); TimestampCaveat issued{now}; - UserIdCaveat client{""}; - // TODO: service caveat when available. - // ServiceCaveat cloud; - + ServiceCaveat client{owner == RootClientTokenOwner::kCloud ? "google.com" + : "privet"}; return CreateMacaroonToken( auth_secret_, now, { @@ -464,7 +462,7 @@ bool AuthManager::CreateAccessTokenFromAuth( auto last_user_id = std::find_if(delegates_rbegin, delegates_rend, [](const UwMacaroonDelegateeInfo& delegatee) { - return !delegatee.is_app; + return delegatee.type == kUwMacaroonDelegateeTypeUser; }); if (last_user_id == delegates_rend || !last_user_id->id_len) { diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index d74abd8..2adb1c2 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -146,21 +146,21 @@ TEST_F(AuthManagerTest, ParseAccessToken) { } TEST_F(AuthManagerTest, GetRootClientAuthToken) { - EXPECT_EQ("WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==", + EXPECT_EQ("WCaDQxkgAUYIGhudoQBIDEZwcml2ZXRQ5aV4jIdY2JGosyU0APnQpA==", Base64Encode( auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentOwner) { EXPECT_EQ( - "WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==", + "WCqDQxkgAUYIGhudoQBMDEpnb29nbGUuY29tUOoLAxSUAZAAv54drarqhag=", Base64Encode(auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud))); } TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) { auto new_time = clock_.Now() + base::TimeDelta::FromDays(15); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("WCCDQxkgAUYIGhuxZ4BCCUBQmNBWA9KdLzxHUCMqzonDZw==", + EXPECT_EQ("WCaDQxkgAUYIGhuxZ4BIDEZwcml2ZXRQsDNy7gcfJT/yvRs3/q40oA==", Base64Encode( auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } @@ -168,7 +168,7 @@ TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) { TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) { AuthManager auth{kSecret2, {}, kSecret1, &clock_}; EXPECT_EQ( - "WCCDQxkgAUYIGhudoQBCCUBQQ/BSJs7FEI260RnwjlJrVw==", + "WCaDQxkgAUYIGhudoQBIDEZwcml2ZXRQKw9xcidyzrelxUkgkLmv1g==", Base64Encode(auth.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } @@ -205,12 +205,12 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuth) { std::vector access_token; AuthScope scope; base::TimeDelta ttl; - auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud); auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), UserInfo{AuthScope::kUser, "234"}); EXPECT_EQ( - "WEWIQxkgAUYIGhudoQBCCUBGCBobnaEARgUaG52k6EIBDkUJQzIzNE0RSzQ2MzMxNTIwMDox" - "UHN8Lm+CUQo7s84Sh+grpAE=", + "WE+IQxkgAUYIGhudoQBMDEpnb29nbGUuY29tRggaG52hAEYFGhudpOhCAQ5FCUMyMzRNEUs0" + "NjMzMTUyMDA6MVCRVKU+0SpOoBppnwqdKMwP", Base64Encode(extended)); EXPECT_TRUE( auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1), -- cgit v1.2.3 From 8585d300b0859dc452151df0d5c9e06652fedab1 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Thu, 28 Jan 2016 15:59:21 -0800 Subject: Remove unused line Change-Id: Ic4742430320e29b7f70f124c20cf9e515678f007 Reviewed-on: https://weave-review.googlesource.com/2390 Reviewed-by: Vitaly Buka --- src/privet/auth_manager.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 71a4f39..f3d32f5 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -320,7 +320,6 @@ std::vector AuthManager::CreateAccessToken(const UserInfo& user_info, { &scope.GetCaveat(), &user.GetCaveat(), &expiration.GetCaveat(), }); - return {}; } bool AuthManager::ParseAccessToken(const std::vector& token, -- cgit v1.2.3 From 3cbb6869edd05975fc876844bfff52d12ac32f66 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Thu, 28 Jan 2016 16:01:13 -0800 Subject: Update macaroon lib with version supporting empty strings Use "" for delegation to the device owner. Change-Id: Ibb4a1da07817eebcbe8b0675381f98af3fdbe947 Reviewed-on: https://weave-review.googlesource.com/2391 Reviewed-by: Vitaly Buka --- src/privet/auth_manager.cc | 2 +- src/privet/auth_manager_unittest.cc | 6 +++--- third_party/libuweave/src/macaroon_caveat.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index f3d32f5..3c08071 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -405,7 +405,7 @@ std::vector AuthManager::GetRootClientAuthToken( TimestampCaveat issued{now}; ServiceCaveat client{owner == RootClientTokenOwner::kCloud ? "google.com" - : "privet"}; + : ""}; return CreateMacaroonToken( auth_secret_, now, { diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index 2adb1c2..d88d033 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -146,7 +146,7 @@ TEST_F(AuthManagerTest, ParseAccessToken) { } TEST_F(AuthManagerTest, GetRootClientAuthToken) { - EXPECT_EQ("WCaDQxkgAUYIGhudoQBIDEZwcml2ZXRQ5aV4jIdY2JGosyU0APnQpA==", + EXPECT_EQ("WCCDQxkgAUYIGhudoQBCDEBQZgRhYq78I8GtFUZHNBbfGw==", Base64Encode( auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } @@ -160,7 +160,7 @@ TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentOwner) { TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) { auto new_time = clock_.Now() + base::TimeDelta::FromDays(15); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("WCaDQxkgAUYIGhuxZ4BIDEZwcml2ZXRQsDNy7gcfJT/yvRs3/q40oA==", + EXPECT_EQ("WCCDQxkgAUYIGhuxZ4BCDEBQjO+OTbjjTzZ/Dvk66nfQqg==", Base64Encode( auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } @@ -168,7 +168,7 @@ TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) { TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) { AuthManager auth{kSecret2, {}, kSecret1, &clock_}; EXPECT_EQ( - "WCaDQxkgAUYIGhudoQBIDEZwcml2ZXRQKw9xcidyzrelxUkgkLmv1g==", + "WCCDQxkgAUYIGhudoQBCDEBQ2MZF8YXv5pbtmMxwz9VtLA==", Base64Encode(auth.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } diff --git a/third_party/libuweave/src/macaroon_caveat.c b/third_party/libuweave/src/macaroon_caveat.c index a2b26dc..3b26b29 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -102,7 +102,7 @@ static bool create_caveat_bstr_value_(UwMacaroonCaveatType type, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { - if (str == NULL || str_len == 0 || buffer == NULL || buffer_size == 0 || + if ((str == NULL && str_len != 0) || buffer == NULL || buffer_size == 0 || new_caveat == NULL || uw_macaroon_caveat_creation_get_buffsize_(type, str_len) > buffer_size) { return false; -- cgit v1.2.3 From 4fe71e314157576d2bdfe54cc6f8ce5df38c571d Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Fri, 29 Jan 2016 11:50:53 -0800 Subject: Make App ID a part of User ID In addition to user ID, auth tokens could be bound to specific app ID. So internal libweave User ID, named UserAppId, from now will consist of auth type, user ID and optional app ID. If operation was called with token containing only user ID, libweave will grant access to all commands for every app for the given user ID. To distinguish between user authorized with local, pairing or anonymous tokens libweave uses UserAppId::type field. As macaroons have no caveats for this kind of information, current implementation will just append the type to the user ID caveat of the access token. BUG: 26292014 Change-Id: I528c2717c95c5daed74bb769b3569fac823761f2 Reviewed-on: https://weave-review.googlesource.com/2394 Reviewed-by: Alex Vakulenko --- src/privet/auth_manager.cc | 88 +++++++++++++++++++-------- src/privet/auth_manager_unittest.cc | 102 +++++++++++++++++++++++--------- src/privet/cloud_delegate.cc | 17 +++--- src/privet/mock_delegates.h | 14 ++++- src/privet/privet_handler_unittest.cc | 6 +- src/privet/privet_types.h | 33 +++++++++-- src/privet/security_manager.cc | 7 ++- src/privet/security_manager_unittest.cc | 3 +- 8 files changed, 197 insertions(+), 73 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 3c08071..0b8a981 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -96,16 +96,26 @@ class ExpirationCaveat : public Caveat { class UserIdCaveat : public Caveat { public: - explicit UserIdCaveat(const std::string& id) + explicit UserIdCaveat(const std::vector& id) : Caveat(kUwMacaroonCaveatTypeDelegateeUser, id.size()) { CHECK(uw_macaroon_caveat_create_delegatee_user_( - reinterpret_cast(id.data()), id.size(), buffer_.data(), - buffer_.size(), &caveat_)); + id.data(), id.size(), buffer_.data(), buffer_.size(), &caveat_)); } DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); }; +class AppIdCaveat : public Caveat { + public: + explicit AppIdCaveat(const std::vector& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeApp, id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_app_( + id.data(), id.size(), buffer_.data(), buffer_.size(), &caveat_)); + } + + DISALLOW_COPY_AND_ASSIGN(AppIdCaveat); +}; + class ServiceCaveat : public Caveat { public: explicit ServiceCaveat(const std::string& id) @@ -312,14 +322,19 @@ AuthManager::~AuthManager() {} std::vector AuthManager::CreateAccessToken(const UserInfo& user_info, base::TimeDelta ttl) const { ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; - UserIdCaveat user{user_info.user_id()}; + // Macaroons have no caveats for auth type. So we just append the type to the + // user ID. + std::vector id_with_type{user_info.id().user}; + id_with_type.push_back(static_cast(user_info.id().type)); + UserIdCaveat user{id_with_type}; + AppIdCaveat app{user_info.id().app}; const base::Time now = Now(); ExpirationCaveat expiration{now + ttl}; - return CreateMacaroonToken( - access_secret_, now, - { - &scope.GetCaveat(), &user.GetCaveat(), &expiration.GetCaveat(), - }); + return CreateMacaroonToken(access_secret_, now, + { + &scope.GetCaveat(), &user.GetCaveat(), + &app.GetCaveat(), &expiration.GetCaveat(), + }); } bool AuthManager::ParseAccessToken(const std::vector& token, @@ -331,7 +346,7 @@ bool AuthManager::ParseAccessToken(const std::vector& token, UwMacaroonValidationResult result{}; const base::Time now = Now(); if (!LoadMacaroon(token, &buffer, &macaroon, error) || - macaroon.num_caveats != 3 || + macaroon.num_caveats != 4 || !VerifyMacaroon(access_secret_, macaroon, now, &result, error)) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, "Invalid token"); @@ -346,12 +361,22 @@ bool AuthManager::ParseAccessToken(const std::vector& token, // If token is valid and token was not extended, it should has precisely this // values. CHECK_GE(FromJ2000Time(result.expiration_time), now); - CHECK_EQ(1u, result.num_delegatees); + CHECK_EQ(2u, result.num_delegatees); CHECK_EQ(kUwMacaroonDelegateeTypeUser, result.delegatees[0].type); - std::string user_id{reinterpret_cast(result.delegatees[0].id), - result.delegatees[0].id_len}; + CHECK_EQ(kUwMacaroonDelegateeTypeApp, result.delegatees[1].type); + CHECK_GT(result.delegatees[0].id_len, 1u); + std::vector user_id{ + result.delegatees[0].id, + result.delegatees[0].id + result.delegatees[0].id_len}; + // Last byte is used for type. See |CreateAccessToken|. + AuthType type = static_cast(user_id.back()); + user_id.pop_back(); + + std::vector app_id{ + result.delegatees[1].id, + result.delegatees[1].id + result.delegatees[1].id_len}; if (user_info) - *user_info = UserInfo{auth_scope, user_id}; + *user_info = UserInfo{auth_scope, UserAppId{type, user_id, app_id}}; return true; } @@ -463,6 +488,11 @@ bool AuthManager::CreateAccessTokenFromAuth( [](const UwMacaroonDelegateeInfo& delegatee) { return delegatee.type == kUwMacaroonDelegateeTypeUser; }); + auto last_app_id = + std::find_if(delegates_rbegin, delegates_rend, + [](const UwMacaroonDelegateeInfo& delegatee) { + return delegatee.type == kUwMacaroonDelegateeTypeApp; + }); if (last_user_id == delegates_rend || !last_user_id->id_len) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, @@ -480,9 +510,13 @@ bool AuthManager::CreateAccessTokenFromAuth( if (!access_token) return true; - std::string user_id{reinterpret_cast(last_user_id->id), - last_user_id->id_len}; - UserInfo info{auth_scope, user_id}; + std::vector user_id{last_user_id->id, + last_user_id->id + last_user_id->id_len}; + std::vector app_id; + if (last_app_id != delegates_rend) + app_id.assign(last_app_id->id, last_app_id->id + last_app_id->id_len); + + UserInfo info{auth_scope, {AuthType::kLocal, user_id, app_id}}; ttl = std::min(ttl, FromJ2000Time(result.expiration_time) - now); *access_token = CreateAccessToken(info, ttl); @@ -519,15 +553,21 @@ std::vector AuthManager::DelegateToUser( TimestampCaveat issued{now}; ExpirationCaveat expiration{now + ttl}; ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; - UserIdCaveat user{user_info.user_id()}; + UserIdCaveat user{user_info.id().user}; + AppIdCaveat app{user_info.id().app}; SessionIdCaveat session{CreateSessionId()}; - return ExtendMacaroonToken( - macaroon, now, - { - &issued.GetCaveat(), &expiration.GetCaveat(), &scope.GetCaveat(), - &user.GetCaveat(), &session.GetCaveat(), - }); + std::vector caveats{ + &issued.GetCaveat(), &expiration.GetCaveat(), &scope.GetCaveat(), + &user.GetCaveat(), + }; + + if (!user_info.id().app.empty()) + caveats.push_back(&app.GetCaveat()); + + caveats.push_back(&session.GetCaveat()); + + return ExtendMacaroonToken(macaroon, now, caveats); } } // namespace privet diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index d88d033..a0a0d01 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -10,6 +10,7 @@ #include "src/config.h" #include "src/data_encoding.h" +#include "src/privet/mock_delegates.h" #include "src/test/mock_clock.h" using testing::Return; @@ -69,49 +70,90 @@ TEST_F(AuthManagerTest, Constructor) { } TEST_F(AuthManagerTest, CreateAccessToken) { - EXPECT_EQ("WCKDQgEURQlDMjM0RgUaG52hAFA3hFh7TexW1jC96sU4CxvN", + EXPECT_EQ("WCaEQgEURglEMjM0AEIKQEYFGhudoQBQaML7svgzFKDXI+/geUUn0w==", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kViewer, "234"}, {}))); - EXPECT_EQ("WCKDQgEIRQlDMjU3RgUaG52hAFD3dEHl3Y9Y28uoUESiYuLq", + UserInfo{AuthScope::kViewer, TestUserId{"234"}}, {}))); + EXPECT_EQ("WCaEQgEIRglEMjU3AEIKQEYFGhudoQBQ0f4NfEW7KDC1QnbExFbf9w==", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kManager, "257"}, {}))); - EXPECT_EQ("WCKDQgECRQlDNDU2RgUaG52hAFBy35bQdtvlqYf+Y/ANyxLU", + UserInfo{AuthScope::kManager, TestUserId{"257"}}, {}))); + EXPECT_EQ("WCaEQgECRglENDU2AEIKQEYFGhudoQBQtgk1ZlsGqs5gF7m+UpwxmQ==", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kOwner, "456"}, {}))); + UserInfo{AuthScope::kOwner, TestUserId{"456"}}, {}))); auto new_time = clock_.Now() + base::TimeDelta::FromDays(11); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("WCKDQgEORQlDMzQ1RgUaG6whgFD1HGVxL8+FPaf/U0bOkXr8", + EXPECT_EQ("WCaEQgEORglEMzQ1AEIKQEYFGhusIYBQl3SG1De+fl2qTquwTl1uRA==", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kUser, "345"}, {}))); + UserInfo{AuthScope::kUser, TestUserId{"345"}}, {}))); } TEST_F(AuthManagerTest, CreateSameToken) { - EXPECT_EQ(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "555"}, {}), - auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "555"}, {})); + EXPECT_EQ(auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, TestUserId{"555"}}, {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, TestUserId{"555"}}, {})); +} + +TEST_F(AuthManagerTest, CreateSameTokenWithApp) { + EXPECT_EQ(auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kLocal, {1, 2, 3}, {4, 5, 6}}}, + {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kLocal, {1, 2, 3}, {4, 5, 6}}}, + {})); +} + +TEST_F(AuthManagerTest, CreateSameTokenWithDifferentType) { + EXPECT_NE(auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kLocal, {1, 2, 3}, {4, 5, 6}}}, + {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kPairing, {1, 2, 3}, {4, 5, 6}}}, + {})); +} + +TEST_F(AuthManagerTest, CreateSameTokenWithDifferentApp) { + EXPECT_NE(auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kLocal, {1, 2, 3}, {4, 5, 6}}}, + {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, + {AuthType::kLocal, {1, 2, 3}, {4, 5, 7}}}, + {})); } TEST_F(AuthManagerTest, CreateTokenDifferentScope) { - EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kViewer, "456"}, {}), - auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "456"}, {})); + EXPECT_NE(auth_.CreateAccessToken( + UserInfo{AuthScope::kViewer, TestUserId{"456"}}, {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kOwner, TestUserId{"456"}}, {})); } TEST_F(AuthManagerTest, CreateTokenDifferentUser) { - EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "456"}, {}), - auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "789"}, {})); + EXPECT_NE(auth_.CreateAccessToken( + UserInfo{AuthScope::kOwner, TestUserId{"456"}}, {}), + auth_.CreateAccessToken( + UserInfo{AuthScope::kOwner, TestUserId{"789"}}, {})); } TEST_F(AuthManagerTest, CreateTokenDifferentTime) { - auto token = auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "567"}, {}); + auto token = auth_.CreateAccessToken( + UserInfo{AuthScope::kOwner, TestUserId{"567"}}, {}); EXPECT_CALL(clock_, Now()) .WillRepeatedly(Return(base::Time::FromTimeT(1400000000))); - EXPECT_NE(token, - auth_.CreateAccessToken(UserInfo{AuthScope::kOwner, "567"}, {})); + EXPECT_NE(token, auth_.CreateAccessToken( + UserInfo{AuthScope::kOwner, TestUserId{"567"}}, {})); } TEST_F(AuthManagerTest, CreateTokenDifferentInstance) { - EXPECT_NE(auth_.CreateAccessToken(UserInfo{AuthScope::kUser, "123"}, {}), + EXPECT_NE(auth_.CreateAccessToken( + UserInfo{AuthScope::kUser, TestUserId{"123"}}, {}), AuthManager({}, {}).CreateAccessToken( - UserInfo{AuthScope::kUser, "123"}, {})); + UserInfo{AuthScope::kUser, TestUserId{"123"}}, {})); } TEST_F(AuthManagerTest, ParseAccessToken) { @@ -122,20 +164,22 @@ TEST_F(AuthManagerTest, ParseAccessToken) { AuthManager auth{{}, {}, {}, &clock_}; - auto token = auth.CreateAccessToken(UserInfo{AuthScope::kUser, "5"}, - base::TimeDelta::FromSeconds(i)); + auto token = + auth.CreateAccessToken(UserInfo{AuthScope::kUser, TestUserId{"5"}}, + base::TimeDelta::FromSeconds(i)); UserInfo user_info; EXPECT_FALSE(auth_.ParseAccessToken(token, &user_info, nullptr)); EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr)); EXPECT_EQ(AuthScope::kUser, user_info.scope()); - EXPECT_EQ("5", user_info.user_id()); + EXPECT_EQ(TestUserId{"5"}, user_info.id()); EXPECT_CALL(clock_, Now()) .WillRepeatedly(Return(kStartTime + base::TimeDelta::FromSeconds(i))); EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr)); - auto extended = DelegateToUser(token, base::TimeDelta::FromSeconds(1000), - UserInfo{AuthScope::kUser, "234"}); + auto extended = + DelegateToUser(token, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, TestUserId{"234"}}); EXPECT_FALSE(auth.ParseAccessToken(extended, &user_info, nullptr)); EXPECT_CALL(clock_, Now()) @@ -207,7 +251,7 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuth) { base::TimeDelta ttl; auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud); auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), - UserInfo{AuthScope::kUser, "234"}); + UserInfo{AuthScope::kUser, TestUserId{"234"}}); EXPECT_EQ( "WE+IQxkgAUYIGhudoQBMDEpnb29nbGUuY29tRggaG52hAEYFGhudpOhCAQ5FCUMyMzRNEUs0" "NjMzMTUyMDA6MVCRVKU+0SpOoBppnwqdKMwP", @@ -220,7 +264,7 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuth) { EXPECT_EQ(scope, user_info.scope()); EXPECT_EQ(AuthScope::kUser, user_info.scope()); - EXPECT_EQ("234", user_info.user_id()); + EXPECT_EQ(TestUserId{"234"}, user_info.id()); } TEST_F(AuthManagerTest, CreateAccessTokenFromAuthNotMinted) { @@ -235,7 +279,7 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuthNotMinted) { TEST_F(AuthManagerTest, CreateAccessTokenFromAuthValidateAfterSomeTime) { auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), - UserInfo{AuthScope::kUser, "234"}); + UserInfo{AuthScope::kUser, TestUserId{"234"}}); // new_time < session_id_expiration < token_expiration. auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(15); @@ -248,7 +292,7 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuthValidateAfterSomeTime) { TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpired) { auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(10), - UserInfo{AuthScope::kUser, "234"}); + UserInfo{AuthScope::kUser, TestUserId{"234"}}); ErrorPtr error; // token_expiration < new_time < session_id_expiration. @@ -263,7 +307,7 @@ TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpired) { TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpiredSessionid) { auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient); auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), - UserInfo{AuthScope::kUser, "234"}); + UserInfo{AuthScope::kUser, TestUserId{"234"}}); ErrorPtr error; // session_id_expiration < new_time < token_expiration. diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc index 5f31fee..49fceaa 100644 --- a/src/privet/cloud_delegate.cc +++ b/src/privet/cloud_delegate.cc @@ -165,7 +165,7 @@ class CloudDelegateImpl : public CloudDelegate { const UserInfo& user_info, const CommandDoneCallback& callback) override { CHECK(user_info.scope() != AuthScope::kNone); - CHECK(!user_info.user_id().empty()); + CHECK(!user_info.id().IsEmpty()); ErrorPtr error; UserRole role; @@ -182,7 +182,7 @@ class CloudDelegateImpl : public CloudDelegate { if (!command_instance) return callback.Run({}, std::move(error)); component_manager_->AddCommand(std::move(command_instance)); - command_owners_[id] = user_info.user_id(); + command_owners_[id] = user_info.id(); callback.Run(*component_manager_->FindCommand(id)->ToJson(), nullptr); } @@ -230,7 +230,7 @@ class CloudDelegateImpl : public CloudDelegate { private: void OnCommandAdded(Command* command) { // Set to "" for any new unknown command. - command_owners_.insert(std::make_pair(command->GetID(), "")); + command_owners_.insert(std::make_pair(command->GetID(), UserAppId{})); } void OnCommandRemoved(Command* command) { @@ -309,14 +309,17 @@ class CloudDelegateImpl : public CloudDelegate { return command; } - bool CanAccessCommand(const std::string& owner_id, + bool CanAccessCommand(const UserAppId& owner, const UserInfo& user_info, ErrorPtr* error) const { CHECK(user_info.scope() != AuthScope::kNone); - CHECK(!user_info.user_id().empty()); + CHECK(!user_info.id().IsEmpty()); if (user_info.scope() == AuthScope::kManager || - owner_id == user_info.user_id()) { + (owner.type == user_info.id().type && + owner.user == user_info.id().user && + (user_info.id().app.empty() || // Token is not restricted to the app. + owner.app == user_info.id().app))) { return true; } @@ -341,7 +344,7 @@ class CloudDelegateImpl : public CloudDelegate { int registation_retry_count_{0}; // Map of command IDs to user IDs. - std::map command_owners_; + std::map command_owners_; // Backoff entry for retrying device registration. BackoffEntry backoff_entry_{®ister_backoff_policy}; diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h index c75d438..c2e9a89 100644 --- a/src/privet/mock_delegates.h +++ b/src/privet/mock_delegates.h @@ -28,6 +28,11 @@ namespace weave { namespace privet { +struct TestUserId : public UserAppId { + TestUserId(const std::string& user_id) + : UserAppId{AuthType::kAnonymous, {user_id.begin(), user_id.end()}, {}} {} +}; + ACTION_TEMPLATE(RunCallback, HAS_1_TEMPLATE_PARAMS(int, k), AND_0_VALUE_PARAMS()) { @@ -103,9 +108,12 @@ class MockSecurityDelegate : public SecurityDelegate { .WillRepeatedly(Return(true)); EXPECT_CALL(*this, ParseAccessToken(_, _, _)) - .WillRepeatedly( - DoAll(SetArgPointee<1>(UserInfo{AuthScope::kViewer, "1234567"}), - Return(true))); + .WillRepeatedly(DoAll(SetArgPointee<1>(UserInfo{ + AuthScope::kViewer, + UserAppId{AuthType::kLocal, + {'1', '2', '3', '4', '5', '6', '7'}, + {}}}), + Return(true))); EXPECT_CALL(*this, GetPairingTypes()) .WillRepeatedly(Return(std::set{ diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc index fa79e77..20f5aa0 100644 --- a/src/privet/privet_handler_unittest.cc +++ b/src/privet/privet_handler_unittest.cc @@ -484,7 +484,8 @@ class PrivetHandlerTestWithAuth : public PrivetHandlerTest { auth_header_ = "Privet 123"; EXPECT_CALL(security_, ParseAccessToken(_, _, _)) .WillRepeatedly(DoAll( - SetArgPointee<1>(UserInfo{AuthScope::kOwner, "1"}), Return(true))); + SetArgPointee<1>(UserInfo{AuthScope::kOwner, TestUserId{"1"}}), + Return(true))); } }; @@ -658,7 +659,8 @@ TEST_F(PrivetHandlerSetupTest, GcdSetup) { TEST_F(PrivetHandlerSetupTest, GcdSetupAsMaster) { EXPECT_CALL(security_, ParseAccessToken(_, _, _)) .WillRepeatedly(DoAll( - SetArgPointee<1>(UserInfo{AuthScope::kManager, "1"}), Return(true))); + SetArgPointee<1>(UserInfo{AuthScope::kManager, TestUserId{"1"}}), + Return(true))); const char kInput[] = R"({ 'gcd': { 'ticketId': 'testTicket', diff --git a/src/privet/privet_types.h b/src/privet/privet_types.h index 49c4522..0f51862 100644 --- a/src/privet/privet_types.h +++ b/src/privet/privet_types.h @@ -29,17 +29,42 @@ enum class WifiType { kWifi50, }; +struct UserAppId { + UserAppId() = default; + + UserAppId(AuthType auth_type, + const std::vector& user_id, + const std::vector& app_id) + : type{auth_type}, + user{user_id}, + app{user_id.empty() ? user_id : app_id} {} + + bool IsEmpty() const { return user.empty(); } + + AuthType type{}; + std::vector user; + std::vector app; +}; + +inline bool operator==(const UserAppId& l, const UserAppId& r) { + return l.user == r.user && l.app == r.app; +} + +inline bool operator!=(const UserAppId& l, const UserAppId& r) { + return l.user != r.user || l.app != r.app; +} + class UserInfo { public: explicit UserInfo(AuthScope scope = AuthScope::kNone, - const std::string& user_id = {}) - : scope_{scope}, user_id_{scope == AuthScope::kNone ? "" : user_id} {} + const UserAppId& id = {}) + : scope_{scope}, id_{scope == AuthScope::kNone ? UserAppId{} : id} {} AuthScope scope() const { return scope_; } - const std::string& user_id() const { return user_id_; } + const UserAppId& id() const { return id_; } private: AuthScope scope_; - std::string user_id_; + UserAppId id_; }; class ConnectionState final { diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc index 04164b3..3b08613 100644 --- a/src/privet/security_manager.cc +++ b/src/privet/security_manager.cc @@ -91,9 +91,10 @@ bool SecurityManager::CreateAccessTokenImpl(AuthType auth_type, std::vector* access_token, AuthScope* access_token_scope, base::TimeDelta* access_token_ttl) { - UserInfo user_info{desired_scope, - std::to_string(static_cast(auth_type)) + "/" + - std::to_string(++last_user_id_)}; + auto user_id = std::to_string(++last_user_id_); + UserInfo user_info{ + desired_scope, + UserAppId{auth_type, {user_id.begin(), user_id.end()}, {}}}; const base::TimeDelta kTtl = base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds); diff --git a/src/privet/security_manager_unittest.cc b/src/privet/security_manager_unittest.cc index 43b7f00..f596de9 100644 --- a/src/privet/security_manager_unittest.cc +++ b/src/privet/security_manager_unittest.cc @@ -25,6 +25,7 @@ #include "src/config.h" #include "src/data_encoding.h" #include "src/privet/auth_manager.h" +#include "src/privet/mock_delegates.h" #include "src/privet/openssl_utils.h" #include "src/test/mock_clock.h" #include "third_party/chromium/crypto/p224_spake.h" @@ -170,7 +171,7 @@ TEST_F(SecurityManagerTest, AccessToken) { UserInfo info; EXPECT_TRUE(security_.ParseAccessToken(token, &info, nullptr)); EXPECT_EQ(requested_scope, info.scope()); - EXPECT_EQ("0/" + std::to_string(i), info.user_id()); + EXPECT_EQ(TestUserId{std::to_string(i)}, info.id()); } } -- cgit v1.2.3 From 4efdf4637d19d6fd6c1725820fd07a5702e71a56 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Fri, 29 Jan 2016 14:21:39 -0800 Subject: Fix GCC warning Change-Id: I7ad142d9873d71e6791dfbbb1ebdff7a6b0f5371 Reviewed-on: https://weave-review.googlesource.com/2396 Reviewed-by: Vitaly Buka --- third_party/libuweave/src/macaroon.c | 3 +-- third_party/libuweave/src/macaroon_caveat.c | 4 +++- third_party/libuweave/src/macaroon_caveat.h | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/third_party/libuweave/src/macaroon.c b/third_party/libuweave/src/macaroon.c index aa775c2..80e5933 100644 --- a/third_party/libuweave/src/macaroon.c +++ b/third_party/libuweave/src/macaroon.c @@ -135,8 +135,7 @@ static void init_validation_result(UwMacaroonValidationResult* result) { /** Reset the result object to the lowest scope when encountering errors */ static void reset_validation_result(UwMacaroonValidationResult* result) { // Start from the largest scope or highest privilege - result->granted_scope = - (UwMacaroonCaveatScopeType)UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE; + result->granted_scope = 0; result->expiration_time = 0; result->weave_app_restricted = true; result->lan_session_id = NULL; diff --git a/third_party/libuweave/src/macaroon_caveat.c b/third_party/libuweave/src/macaroon_caveat.c index 3b26b29..85d8bbb 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -12,6 +12,9 @@ #include "src/macaroon_context.h" #include "src/macaroon_encoding.h" +// For security sanity checks +#define UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE 127 + static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { switch (type) { case kUwMacaroonCaveatTypeNonce: @@ -507,7 +510,6 @@ bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, case kUwMacaroonCaveatTypeScope: if (!uw_macaroon_caveat_get_value_uint_(caveat, &scope) || // Larger value means less priviledge - scope < UW_MACAROON_CAVEAT_SCOPE_HIGHEST_POSSIBLE || scope > UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE) { return false; } diff --git a/third_party/libuweave/src/macaroon_caveat.h b/third_party/libuweave/src/macaroon_caveat.h index b6846e8..33f9d24 100644 --- a/third_party/libuweave/src/macaroon_caveat.h +++ b/third_party/libuweave/src/macaroon_caveat.h @@ -33,10 +33,6 @@ typedef enum { kUwMacaroonCaveatTypeServerAuthenticationTokenV1 = 12289, // bstr (0x3001) } UwMacaroonCaveatType; -// For security sanity checks -#define UW_MACAROON_CAVEAT_SCOPE_HIGHEST_POSSIBLE 0 -#define UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE 127 - typedef enum { kUwMacaroonCaveatScopeTypeOwner = 2, kUwMacaroonCaveatScopeTypeManager = 8, -- cgit v1.2.3 From 08be74de678930e6823f9fe7e460c35bb58040f9 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Tue, 2 Feb 2016 15:25:09 -0800 Subject: Update libuweave/macaroon code Added delegation time stamp into access token to match changed validation logic of macaroons. BUG: 26728665 Change-Id: I131b92b0e0b1b2274d80bdc0b5790a8c05071ec5 Reviewed-on: https://weave-review.googlesource.com/2467 Reviewed-by: Vitaly Buka --- src/privet/auth_manager.cc | 17 +++++---- src/privet/auth_manager_unittest.cc | 8 ++--- third_party/libuweave/src/macaroon.c | 40 ++++++++++++---------- third_party/libuweave/src/macaroon.h | 4 ++- third_party/libuweave/src/macaroon_caveat.c | 19 +++++----- third_party/libuweave/src/macaroon_caveat.h | 3 ++ .../libuweave/src/macaroon_caveat_internal.h | 3 +- 7 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 0b8a981..c82887e 100644 --- a/src/privet/auth_manager.cc +++ b/src/privet/auth_manager.cc @@ -321,6 +321,8 @@ AuthManager::~AuthManager() {} std::vector AuthManager::CreateAccessToken(const UserInfo& user_info, base::TimeDelta ttl) const { + const base::Time now = Now(); + TimestampCaveat issued{now}; ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; // Macaroons have no caveats for auth type. So we just append the type to the // user ID. @@ -328,13 +330,14 @@ std::vector AuthManager::CreateAccessToken(const UserInfo& user_info, id_with_type.push_back(static_cast(user_info.id().type)); UserIdCaveat user{id_with_type}; AppIdCaveat app{user_info.id().app}; - const base::Time now = Now(); ExpirationCaveat expiration{now + ttl}; - return CreateMacaroonToken(access_secret_, now, - { - &scope.GetCaveat(), &user.GetCaveat(), - &app.GetCaveat(), &expiration.GetCaveat(), - }); + return CreateMacaroonToken( + access_secret_, now, + { + + &issued.GetCaveat(), &scope.GetCaveat(), &user.GetCaveat(), + &app.GetCaveat(), &expiration.GetCaveat(), + }); } bool AuthManager::ParseAccessToken(const std::vector& token, @@ -346,7 +349,7 @@ bool AuthManager::ParseAccessToken(const std::vector& token, UwMacaroonValidationResult result{}; const base::Time now = Now(); if (!LoadMacaroon(token, &buffer, &macaroon, error) || - macaroon.num_caveats != 4 || + macaroon.num_caveats != 5 || !VerifyMacaroon(access_secret_, macaroon, now, &result, error)) { return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, "Invalid token"); diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index a0a0d01..294aefa 100644 --- a/src/privet/auth_manager_unittest.cc +++ b/src/privet/auth_manager_unittest.cc @@ -70,18 +70,18 @@ TEST_F(AuthManagerTest, Constructor) { } TEST_F(AuthManagerTest, CreateAccessToken) { - EXPECT_EQ("WCaEQgEURglEMjM0AEIKQEYFGhudoQBQaML7svgzFKDXI+/geUUn0w==", + EXPECT_EQ("WC2FRggaG52hAEIBFEYJRDIzNABCCkBGBRobnaEAUFAF46oQlMmXgnLstt7wU2w=", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kViewer, TestUserId{"234"}}, {}))); - EXPECT_EQ("WCaEQgEIRglEMjU3AEIKQEYFGhudoQBQ0f4NfEW7KDC1QnbExFbf9w==", + EXPECT_EQ("WC2FRggaG52hAEIBCEYJRDI1NwBCCkBGBRobnaEAUEdWRNHcu/0mA6c3e0tgDrk=", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kManager, TestUserId{"257"}}, {}))); - EXPECT_EQ("WCaEQgECRglENDU2AEIKQEYFGhudoQBQtgk1ZlsGqs5gF7m+UpwxmQ==", + EXPECT_EQ("WC2FRggaG52hAEIBAkYJRDQ1NgBCCkBGBRobnaEAUH2ZLgUPdTtjNRa+PoDkMW4=", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kOwner, TestUserId{"456"}}, {}))); auto new_time = clock_.Now() + base::TimeDelta::FromDays(11); EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time)); - EXPECT_EQ("WCaEQgEORglEMzQ1AEIKQEYFGhusIYBQl3SG1De+fl2qTquwTl1uRA==", + EXPECT_EQ("WC2FRggaG6whgEIBDkYJRDM0NQBCCkBGBRobrCGAUDAFptj7bbYmbpaa6Wpb1Wo=", Base64Encode(auth_.CreateAccessToken( UserInfo{AuthScope::kUser, TestUserId{"345"}}, {}))); } diff --git a/third_party/libuweave/src/macaroon.c b/third_party/libuweave/src/macaroon.c index 80e5933..c823804 100644 --- a/third_party/libuweave/src/macaroon.c +++ b/third_party/libuweave/src/macaroon.c @@ -124,29 +124,32 @@ bool uw_macaroon_extend_(const UwMacaroon* old_macaroon, static void init_validation_result(UwMacaroonValidationResult* result) { // Start from the largest scope - result->granted_scope = kUwMacaroonCaveatScopeTypeOwner; - result->expiration_time = UINT32_MAX; - result->weave_app_restricted = false; - result->lan_session_id = NULL; - result->lan_session_id_len = 0; - result->num_delegatees = 0; + *result = (UwMacaroonValidationResult){ + .granted_scope = kUwMacaroonCaveatScopeTypeOwner, + .expiration_time = UINT32_MAX, + }; } /** Reset the result object to the lowest scope when encountering errors */ static void reset_validation_result(UwMacaroonValidationResult* result) { - // Start from the largest scope or highest privilege - result->granted_scope = 0; - result->expiration_time = 0; - result->weave_app_restricted = true; - result->lan_session_id = NULL; - result->lan_session_id_len = 0; - - result->num_delegatees = 0; - for (size_t i = 0; i < MAX_NUM_DELEGATEES; i++) { - result->delegatees[i].id = NULL; - result->delegatees[i].id_len = 0; - result->delegatees[i].type = kUwMacaroonDelegateeTypeNone; + *result = (UwMacaroonValidationResult){ + .weave_app_restricted = true, + .granted_scope = UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE}; +} + +/** Get the next closest scope (to the narrower side). */ +static UwMacaroonCaveatScopeType get_closest_scope( + UwMacaroonCaveatScopeType scope) { + if (scope <= kUwMacaroonCaveatScopeTypeOwner) { + return kUwMacaroonCaveatScopeTypeOwner; + } else if (scope <= kUwMacaroonCaveatScopeTypeManager) { + return kUwMacaroonCaveatScopeTypeManager; + } else if (scope <= kUwMacaroonCaveatScopeTypeUser) { + return kUwMacaroonCaveatScopeTypeUser; + } else if (scope <= kUwMacaroonCaveatScopeTypeViewer) { + return kUwMacaroonCaveatScopeTypeViewer; } + return scope; } bool uw_macaroon_validate_(const UwMacaroon* macaroon, @@ -178,6 +181,7 @@ bool uw_macaroon_validate_(const UwMacaroon* macaroon, } } + result->granted_scope = get_closest_scope(result->granted_scope); return true; } diff --git a/third_party/libuweave/src/macaroon.h b/third_party/libuweave/src/macaroon.h index c93bbb2..c739bca 100644 --- a/third_party/libuweave/src/macaroon.h +++ b/third_party/libuweave/src/macaroon.h @@ -36,6 +36,7 @@ typedef struct { const uint8_t* id; size_t id_len; UwMacaroonDelegateeType type; + uint32_t timestamp; } UwMacaroonDelegateeInfo; #define MAX_NUM_DELEGATEES 10 @@ -67,7 +68,8 @@ bool uw_macaroon_extend_(const UwMacaroon* old_macaroon, /** * Verify and validate the Macaroon, and put relevant information into the - * result object. + * result object. Note that the resulting granted_scope will be the closest + * valid scope type (to the narrower side) defined in macaroon_caveat.h. */ bool uw_macaroon_validate_( const UwMacaroon* macaroon, diff --git a/third_party/libuweave/src/macaroon_caveat.c b/third_party/libuweave/src/macaroon_caveat.c index 85d8bbb..dc4ee3b 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -12,9 +12,6 @@ #include "src/macaroon_context.h" #include "src/macaroon_encoding.h" -// For security sanity checks -#define UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE 127 - static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { switch (type) { case kUwMacaroonCaveatTypeNonce: @@ -403,8 +400,9 @@ static bool update_and_check_expiration_time( static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, const UwMacaroonCaveat* caveat, + uint32_t issued_time, UwMacaroonValidationResult* result) { - if (result->num_delegatees >= MAX_NUM_DELEGATEES) { + if (result->num_delegatees >= MAX_NUM_DELEGATEES || issued_time == 0) { return false; } @@ -441,6 +439,7 @@ static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, return false; } result->delegatees[result->num_delegatees].type = delegatee_type; + result->delegatees[result->num_delegatees].timestamp = issued_time; result->num_delegatees++; return true; } @@ -471,22 +470,22 @@ bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, return true; case kUwMacaroonCaveatTypeDelegationTimestamp: - state->has_issued_time = true; - if (!uw_macaroon_caveat_get_value_uint_(caveat, &issued_time)) { + if (!uw_macaroon_caveat_get_value_uint_(caveat, &issued_time) || + issued_time < state->issued_time) { return false; } state->issued_time = issued_time; return true; case kUwMacaroonCaveatTypeTTL1Hour: - if (!(state->has_issued_time)) { + if (state->issued_time == 0) { return false; } return update_and_check_expiration_time( context->current_time, state->issued_time + 60 * 60, result); case kUwMacaroonCaveatTypeTTL24Hour: - if (!(state->has_issued_time)) { + if (state->issued_time == 0) { return false; } return update_and_check_expiration_time( @@ -496,7 +495,8 @@ bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: case kUwMacaroonCaveatTypeDelegateeService: - return update_delegatee_list(caveat_type, caveat, result); + return update_delegatee_list(caveat_type, caveat, state->issued_time, + result); // Time related caveats case kUwMacaroonCaveatTypeExpirationAbsolute: @@ -596,7 +596,6 @@ bool uw_macaroon_caveat_init_validation_state_( return false; } - state->has_issued_time = false; state->issued_time = 0; return true; } diff --git a/third_party/libuweave/src/macaroon_caveat.h b/third_party/libuweave/src/macaroon_caveat.h index 33f9d24..4905667 100644 --- a/third_party/libuweave/src/macaroon_caveat.h +++ b/third_party/libuweave/src/macaroon_caveat.h @@ -40,6 +40,9 @@ typedef enum { kUwMacaroonCaveatScopeTypeViewer = 20, } UwMacaroonCaveatScopeType; +// For security sanity checks +#define UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE 127 + /** Compute the buffer sizes that are enough for caveat creation functions. */ size_t uw_macaroon_caveat_creation_get_buffsize_(UwMacaroonCaveatType type, size_t str_len); diff --git a/third_party/libuweave/src/macaroon_caveat_internal.h b/third_party/libuweave/src/macaroon_caveat_internal.h index 46a72fb..d6e7b07 100644 --- a/third_party/libuweave/src/macaroon_caveat_internal.h +++ b/third_party/libuweave/src/macaroon_caveat_internal.h @@ -20,8 +20,7 @@ bool uw_macaroon_caveat_sign_(const uint8_t* key, size_t mac_tag_size); typedef struct { - bool has_issued_time; - uint32_t issued_time; + uint32_t issued_time; // 0 when invalid or not set. } UwMacaroonValidationState; bool uw_macaroon_caveat_init_validation_state_( -- cgit v1.2.3