diff options
51 files changed, 2698 insertions, 628 deletions
@@ -37,7 +37,7 @@ CFLAGS := \ -Wwrite-strings CFLAGS_Debug := \ - -O0 \ + -O0 \ -g3 CFLAGS_Release := \ @@ -77,16 +77,18 @@ sudo apt-get install \ ### For tests + - cmake - gtest (included; see third_party/get_gtest.sh) - gmock (included; see third_party/get_gtest.sh) ### For examples + - cmake - hostapd - libavahi-client-dev - libcurl4-openssl-dev - - libevent 2.0.x - libevhtp (included; see third_party/get_libevhtp.sh) + - libevent-dev # Compiling diff --git a/examples/examples.mk b/examples/examples.mk index f1e92b6..af15d5c 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -7,7 +7,13 @@ examples_provider_obj_files := $(EXAMPLES_PROVIDER_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o) -$(examples_provider_obj_files) : out/$(BUILD_MODE)/%.o : %.cc third_party/include/evhtp.h +USE_INTERNAL_LIBEVHTP ?= 1 + +ifeq (1, $(USE_INTERNAL_LIBEVHTP)) +$(examples_provider_obj_files) : third_party/include/evhtp.h +endif + +$(examples_provider_obj_files) : out/$(BUILD_MODE)/%.o : %.cc mkdir -p $(dir $@) $(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $< @@ -15,7 +21,21 @@ out/$(BUILD_MODE)/examples_provider.a : $(examples_provider_obj_files) rm -f $@ $(AR) crsT $@ $^ -out/$(BUILD_MODE)/examples/daemon/%.o : examples/daemon/%.cc third_party/include/evhtp.h +EXAMPLES_DAEMON_SRC_FILES := \ + examples/daemon/ledflasher/ledflasher.cc \ + examples/daemon/light/light.cc \ + examples/daemon/lock/lock.cc \ + examples/daemon/oven/oven.cc \ + examples/daemon/sample/sample.cc \ + examples/daemon/speaker/speaker.cc + +examples_daemon_obj_files := $(EXAMPLES_DAEMON_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o) + +ifeq (1, $(USE_INTERNAL_LIBEVHTP)) +$(examples_daemon_obj_files) : third_party/include/evhtp.h +endif + +$(examples_daemon_obj_files) : out/$(BUILD_MODE)/%.o : %.cc mkdir -p $(dir $@) $(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $< @@ -32,22 +52,30 @@ daemon_common_flags := \ -lssl \ -lcrypto -out/$(BUILD_MODE)/weave_daemon_ledflasher : out/$(BUILD_MODE)/examples/daemon/ledflasher/ledflasher.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +daemon_deps := out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so + +ifeq (1, $(USE_INTERNAL_LIBEVHTP)) +daemon_deps += third_party/lib/libevhtp.a +else +daemon_common_flags += -levhtp +endif + +out/$(BUILD_MODE)/weave_daemon_ledflasher : out/$(BUILD_MODE)/examples/daemon/ledflasher/ledflasher.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) -out/$(BUILD_MODE)/weave_daemon_light : out/$(BUILD_MODE)/examples/daemon/light/light.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +out/$(BUILD_MODE)/weave_daemon_light : out/$(BUILD_MODE)/examples/daemon/light/light.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) -out/$(BUILD_MODE)/weave_daemon_lock : out/$(BUILD_MODE)/examples/daemon/lock/lock.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +out/$(BUILD_MODE)/weave_daemon_lock : out/$(BUILD_MODE)/examples/daemon/lock/lock.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) -out/$(BUILD_MODE)/weave_daemon_oven : out/$(BUILD_MODE)/examples/daemon/oven/oven.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +out/$(BUILD_MODE)/weave_daemon_oven : out/$(BUILD_MODE)/examples/daemon/oven/oven.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) -out/$(BUILD_MODE)/weave_daemon_sample : out/$(BUILD_MODE)/examples/daemon/sample/sample.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +out/$(BUILD_MODE)/weave_daemon_sample : out/$(BUILD_MODE)/examples/daemon/sample/sample.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) -out/$(BUILD_MODE)/weave_daemon_speaker : out/$(BUILD_MODE)/examples/daemon/speaker/speaker.o out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so third_party/lib/libevhtp.a +out/$(BUILD_MODE)/weave_daemon_speaker : out/$(BUILD_MODE)/examples/daemon/speaker/speaker.o $(daemon_deps) $(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags) all-examples : out/$(BUILD_MODE)/weave_daemon_ledflasher out/$(BUILD_MODE)/weave_daemon_light out/$(BUILD_MODE)/weave_daemon_lock out/$(BUILD_MODE)/weave_daemon_oven out/$(BUILD_MODE)/weave_daemon_sample out/$(BUILD_MODE)/weave_daemon_speaker diff --git a/examples/prerequisites.sh b/examples/prerequisites.sh index 489bb58..23d54d7 100755 --- a/examples/prerequisites.sh +++ b/examples/prerequisites.sh @@ -13,10 +13,12 @@ sudo apt-get update && sudo apt-get install ${APT_GET_OPTS} \ autoconf \ automake \ binutils \ + cmake \ g++ \ hostapd \ libavahi-client-dev \ libcurl4-openssl-dev \ + libevent-dev \ libexpat1-dev \ libnl-3-dev \ libnl-route-3-dev \ diff --git a/examples/provider/event_http_server.cc b/examples/provider/event_http_server.cc index 1bf58f6..c058401 100644 --- a/examples/provider/event_http_server.cc +++ b/examples/provider/event_http_server.cc @@ -38,7 +38,7 @@ class HttpServerImpl::RequestImpl : public Request { ~RequestImpl() {} - std::string GetPath() const override { return req_->uri->path->path; } + std::string GetPath() const override { return req_->uri->path->full; } std::string GetFirstHeader(const std::string& name) const override { const char* header = evhtp_header_find(req_->headers_in, name.c_str()); @@ -132,7 +132,7 @@ void HttpServerImpl::GenerateX509(X509* x509, EVP_PKEY* pkey) { void HttpServerImpl::NotFound(evhtp_request_t* req) { EventPtr<evbuffer> buf{evbuffer_new()}; - evbuffer_add_printf(buf.get(), "404 Not Found: %s\n", req->uri->path->path); + evbuffer_add_printf(buf.get(), "404 Not Found: %s\n", req->uri->path->full); evhtp_send_reply_start(req, 404); evhtp_send_reply_body(req, buf.get()); evhtp_send_reply_end(req); diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc index a6c2e60..b215023 100644 --- a/examples/provider/file_config_store.cc +++ b/examples/provider/file_config_store.cc @@ -26,7 +26,7 @@ FileConfigStore::FileConfigStore(const std::string& model_id, std::string FileConfigStore::GetPath(const std::string& name) const { std::string path{kSettingsDir}; - path += path + "weave_settings_" + model_id_; + path += "weave_settings_" + model_id_; if (!name.empty()) path += "_" + name; return path + ".json"; diff --git a/file_lists.mk b/file_lists.mk index b944c3a..9a015e2 100644 --- a/file_lists.mk +++ b/file_lists.mk @@ -3,6 +3,8 @@ # found in the LICENSE file. 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 \ @@ -48,6 +50,8 @@ WEAVE_TEST_SRC_FILES := \ src/test/unittest_utils.cc 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 \ @@ -161,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/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()) diff --git a/include/weave/settings.h b/include/weave/settings.h index 741fff2..7cb798d 100644 --- a/include/weave/settings.h +++ b/include/weave/settings.h @@ -61,6 +61,7 @@ struct Settings { // Optional cloud information. Can be used for testing or debugging. std::string oauth_url; std::string service_url; + std::string xmpp_endpoint; // Cloud ID of the registered device. Empty if device is not registered. std::string cloud_id; diff --git a/src/access_api_handler.cc b/src/access_api_handler.cc new file mode 100644 index 0000000..7c39b20 --- /dev/null +++ b/src/access_api_handler.cc @@ -0,0 +1,227 @@ +// 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 <base/bind.h> +#include <weave/device.h> + +#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<uint8_t>* user_id_decoded, + std::vector<uint8_t>* 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::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::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<Command>& 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<uint8_t> user_id; + std::vector<uint8_t> 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<Command>& 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<uint8_t> user_id; + std::vector<uint8_t> app_id; + ErrorPtr error; + if (!GetIds(parameters, &user_id, &app_id, &error)) { + command->Abort(error.get(), nullptr); + return; + } + + manager_->Unblock(user_id, app_id, + base::Bind(&AccessApiHandler::OnCommandDone, + weak_ptr_factory_.GetWeakPtr(), cmd)); +} + +void AccessApiHandler::List(const std::weak_ptr<Command>& 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<base::ListValue> entries{new base::ListValue}; + for (const auto& e : manager_->GetEntries()) { + std::unique_ptr<base::DictionaryValue> 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<Command>& 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 <memory> + +#include <base/memory/weak_ptr.h> +#include <weave/error.h> + +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>& command); + void Unblock(const std::weak_ptr<Command>& command); + void List(const std::weak_ptr<Command>& command); + void UpdateState(); + + void OnCommandDone(const std::weak_ptr<Command>& command, ErrorPtr error); + + Device* device_{nullptr}; + AccessBlackListManager* manager_{nullptr}; + + base::WeakPtrFactory<AccessApiHandler> 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..3e7f5d7 --- /dev/null +++ b/src/access_api_handler_unittest.cc @@ -0,0 +1,259 @@ +// 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 <gtest/gtest.h> +#include <weave/provider/test/fake_task_runner.h> +#include <weave/test/mock_device.h> +#include <weave/test/unittest_utils.h> + +#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<uint8_t>&, + const std::vector<uint8_t>&, + const base::Time&, + const DoneCallback&)); + MOCK_METHOD3(Unblock, + void(const std::vector<uint8_t>&, + const std::vector<uint8_t>&, + const DoneCallback&)); + MOCK_CONST_METHOD2(IsBlocked, + bool(const std::vector<uint8_t>&, + const std::vector<uint8_t>&)); + MOCK_CONST_METHOD0(GetEntries, std::vector<Entry>()); + 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<std::string>& 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<base::DictionaryValue> 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<base::DictionaryValue>{state->DeepCopy()}; + } + + StrictMock<provider::test::FakeTaskRunner> task_runner_; + ComponentManagerImpl component_manager_{&task_runner_}; + StrictMock<test::MockDevice> device_; + StrictMock<MockAccessBlackListManager> access_manager_; + std::unique_ptr<AccessApiHandler> 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); + + expected = R"({ + "capacity": 10, + "size": 0 + })"; + EXPECT_JSON_EQ(expected, *GetState()); +} + +TEST_F(AccessApiHandlerTest, Block) { + EXPECT_CALL(access_manager_, Block(std::vector<uint8_t>{1, 2, 3}, + std::vector<uint8_t>{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 + } + })"); + + auto expected = R"({ + "capacity": 10, + "size": 1 + })"; + EXPECT_JSON_EQ(expected, *GetState()); +} + +TEST_F(AccessApiHandlerTest, Unblock) { + EXPECT_CALL(access_manager_, Unblock(std::vector<uint8_t>{1, 2, 3}, + std::vector<uint8_t>{3, 4, 5}, _)) + .WillOnce(WithArgs<2>( + Invoke([](const DoneCallback& callback) { callback.Run(nullptr); }))); + EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4)); + + AddCommand(R"({ + 'name' : '_accessControlBlackList.unblock', + 'component': 'accessControl', + 'parameters': { + 'userId': 'AQID', + 'applicationId': 'AwQF', + 'expirationTimeoutSec': 1234 + } + })"); + + auto expected = R"({ + "capacity": 10, + "size": 4 + })"; + EXPECT_JSON_EQ(expected, *GetState()); +} + +TEST_F(AccessApiHandlerTest, List) { + std::vector<AccessBlackListManager::Entry> 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" + } ] + })"; + + const auto& results = AddCommand(R"({ + 'name' : '_accessControlBlackList.list', + 'component': 'accessControl', + 'parameters': { + } + })"); + + EXPECT_JSON_EQ(expected, results); +} +} // namespace weave diff --git a/src/access_black_list_manager.h b/src/access_black_list_manager.h new file mode 100644 index 0000000..b56226a --- /dev/null +++ b/src/access_black_list_manager.h @@ -0,0 +1,56 @@ +// 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 <vector> + +#include <base/time/time.h> + +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<uint8_t> user_id; + std::vector<uint8_t> app_id; + + // Time after which to discard the rule. + base::Time expiration; + }; + virtual ~AccessBlackListManager() = default; + + virtual void Block(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const base::Time& expiration, + const DoneCallback& callback) = 0; + virtual void Unblock(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const DoneCallback& callback) = 0; + virtual bool IsBlocked(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id) const = 0; + virtual std::vector<Entry> 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_ diff --git a/src/access_black_list_manager_impl.cc b/src/access_black_list_manager_impl.cc new file mode 100644 index 0000000..992a680 --- /dev/null +++ b/src/access_black_list_manager_impl.cc @@ -0,0 +1,163 @@ +// 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 <base/json/json_reader.h> +#include <base/json/json_writer.h> +#include <base/values.h> + +#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<base::DictionaryValue> 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<uint8_t>& user_id, + const std::vector<uint8_t>& 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, "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, "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<uint8_t>& user_id, + const std::vector<uint8_t>& 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, "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<uint8_t>& user_id, + const std::vector<uint8_t>& 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<AccessBlackListManager::Entry> +AccessBlackListManagerImpl::GetEntries() const { + std::vector<Entry> 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 <map> +#include <utility> + +#include <base/time/default_clock.h> +#include <base/time/time.h> +#include <weave/error.h> +#include <weave/provider/config_store.h> + +#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<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const base::Time& expiration, + const DoneCallback& callback) override; + void Unblock(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const DoneCallback& callback) override; + bool IsBlocked(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id) const override; + std::vector<Entry> 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::pair<std::vector<uint8_t>, std::vector<uint8_t>>, 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..fd9f226 --- /dev/null +++ b/src/access_black_list_manager_impl_unittest.cc @@ -0,0 +1,165 @@ +// 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 <gmock/gmock.h> +#include <gtest/gtest.h> +#include <weave/provider/test/mock_config_store.h> +#include <weave/test/unittest_utils.h> + +#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<test::MockClock> clock_; + StrictMock<provider::test::MockConfigStore> config_store_{false}; + std::unique_ptr<AccessBlackListManagerImpl> manager_; +}; + +TEST_F(AccessBlackListManagerImplTest, Init) { + EXPECT_EQ(1u, manager_->GetSize()); + EXPECT_EQ(10u, manager_->GetCapacity()); + EXPECT_EQ((std::vector<AccessBlackListManagerImpl::Entry>{{ + {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("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<uint8_t>(i / 256), static_cast<uint8_t>(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("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("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<uint8_t>, std::vector<uint8_t>>> { + 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<uint8_t>{}, + std::vector<uint8_t>{7, 7, 7}), + testing::Values(std::vector<uint8_t>{}, + std::vector<uint8_t>{8, 8, 8}))); + +} // namespace weave 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<BaseApiHandler> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(BaseApiHandler); diff --git a/src/config.cc b/src/config.cc index 44d20dd..21a1c1f 100644 --- a/src/config.cc +++ b/src/config.cc @@ -33,6 +33,7 @@ const char kClientSecret[] = "client_secret"; const char kApiKey[] = "api_key"; const char kOAuthURL[] = "oauth_url"; const char kServiceURL[] = "service_url"; +const char kXmppEndpoint[] = "xmpp_endpoint"; const char kName[] = "name"; const char kDescription[] = "description"; const char kLocation[] = "location"; @@ -51,6 +52,7 @@ const char kRootClientTokenOwner[] = "root_client_token_owner"; const char kWeaveUrl[] = "https://www.googleapis.com/weave/v1/"; const char kDeprecatedUrl[] = "https://www.googleapis.com/clouddevices/v1/"; +const char kXmppEndpoint[] = "talk.google.com:5223"; namespace { @@ -69,6 +71,7 @@ Config::Settings CreateDefaultSettings() { Config::Settings result; result.oauth_url = "https://accounts.google.com/o/oauth2/"; result.service_url = kWeaveUrl; + result.xmpp_endpoint = kXmppEndpoint; result.local_anonymous_access_role = AuthScope::kViewer; result.pairing_modes.insert(PairingType::kPinCode); result.device_id = base::GenerateGUID(); @@ -119,6 +122,7 @@ void Config::Load() { CHECK(!settings_.api_key.empty()); CHECK(!settings_.oauth_url.empty()); CHECK(!settings_.service_url.empty()); + CHECK(!settings_.xmpp_endpoint.empty()); CHECK(!settings_.oem_name.empty()); CHECK(!settings_.model_name.empty()); CHECK(!settings_.model_id.empty()); @@ -190,6 +194,10 @@ void Config::Transaction::LoadState() { set_service_url(tmp); } + if (dict->GetString(config_keys::kXmppEndpoint, &tmp)) { + set_xmpp_endpoint(tmp); + } + if (dict->GetString(config_keys::kName, &tmp)) set_name(tmp); @@ -249,6 +257,7 @@ void Config::Save() { dict.SetString(config_keys::kApiKey, settings_.api_key); dict.SetString(config_keys::kOAuthURL, settings_.oauth_url); dict.SetString(config_keys::kServiceURL, settings_.service_url); + dict.SetString(config_keys::kXmppEndpoint, settings_.xmpp_endpoint); dict.SetString(config_keys::kRefreshToken, settings_.refresh_token); dict.SetString(config_keys::kCloudId, settings_.cloud_id); dict.SetString(config_keys::kDeviceId, settings_.device_id); diff --git a/src/config.h b/src/config.h index 6dc0a07..8e0a8f3 100644 --- a/src/config.h +++ b/src/config.h @@ -68,6 +68,9 @@ class Config final { void set_service_url(const std::string& url) { settings_->service_url = url; } + void set_xmpp_endpoint(const std::string& endpoint) { + settings_->xmpp_endpoint = endpoint; + } void set_name(const std::string& name) { settings_->name = name; } void set_description(const std::string& description) { settings_->description = description; diff --git a/src/config_unittest.cc b/src/config_unittest.cc index 4b0e5b4..bb2743a 100644 --- a/src/config_unittest.cc +++ b/src/config_unittest.cc @@ -62,6 +62,7 @@ TEST_F(ConfigTest, Defaults) { EXPECT_EQ("", GetSettings().api_key); EXPECT_EQ("https://accounts.google.com/o/oauth2/", GetSettings().oauth_url); EXPECT_EQ("https://www.googleapis.com/weave/v1/", GetSettings().service_url); + EXPECT_EQ("talk.google.com:5223", GetSettings().xmpp_endpoint); EXPECT_EQ("", GetSettings().oem_name); EXPECT_EQ("", GetSettings().model_name); EXPECT_EQ("", GetSettings().model_id); @@ -146,7 +147,8 @@ TEST_F(ConfigTest, LoadState) { "refresh_token": "state_refresh_token", "robot_account": "state_robot_account", "secret": "c3RhdGVfc2VjcmV0", - "service_url": "state_service_url" + "service_url": "state_service_url", + "xmpp_endpoint": "state_xmpp_endpoint" })"; EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return(state)); @@ -157,6 +159,7 @@ TEST_F(ConfigTest, LoadState) { EXPECT_EQ("state_api_key", GetSettings().api_key); EXPECT_EQ("state_oauth_url", GetSettings().oauth_url); EXPECT_EQ("state_service_url", GetSettings().service_url); + EXPECT_EQ("state_xmpp_endpoint", GetSettings().xmpp_endpoint); EXPECT_EQ(GetDefaultSettings().oem_name, GetSettings().oem_name); EXPECT_EQ(GetDefaultSettings().model_name, GetSettings().model_name); EXPECT_EQ(GetDefaultSettings().model_id, GetSettings().model_id); @@ -200,6 +203,9 @@ TEST_F(ConfigTest, Setters) { change.set_service_url("set_service_url"); EXPECT_EQ("set_service_url", GetSettings().service_url); + change.set_xmpp_endpoint("set_xmpp_endpoint"); + EXPECT_EQ("set_xmpp_endpoint", GetSettings().xmpp_endpoint); + change.set_name("set_name"); EXPECT_EQ("set_name", GetSettings().name); @@ -277,7 +283,8 @@ TEST_F(ConfigTest, Setters) { 'refresh_token': 'set_token', 'robot_account': 'set_account', 'secret': 'AQIDBAU=', - 'service_url': 'set_service_url' + 'service_url': 'set_service_url', + 'xmpp_endpoint': 'set_xmpp_endpoint' })"; EXPECT_JSON_EQ(expected, *test::CreateValue(json)); callback.Run(nullptr); diff --git a/src/device_manager.cc b/src/device_manager.cc index 097f854..deb5404 100644 --- a/src/device_manager.cc +++ b/src/device_manager.cc @@ -8,6 +8,8 @@ #include <base/bind.h> +#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<ComponentManager> component_manager_; std::unique_ptr<DeviceRegistrationInfo> device_info_; std::unique_ptr<BaseApiHandler> base_api_handler_; + std::unique_ptr<AccessBlackListManager> black_list_manager_; + std::unique_ptr<AccessApiHandler> access_api_handler_; std::unique_ptr<privet::Manager> privet_; base::WeakPtrFactory<DeviceManager> weak_ptr_factory_{this}; diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc index 7c20084..0dc1f54 100644 --- a/src/device_registration_info.cc +++ b/src/device_registration_info.cc @@ -463,8 +463,9 @@ void DeviceRegistrationInfo::StartNotificationChannel() { current_notification_channel_ = pull_channel_.get(); notification_channel_starting_ = true; - primary_notification_channel_.reset(new XmppChannel{ - GetSettings().robot_account, access_token_, task_runner_, network_}); + primary_notification_channel_.reset( + new XmppChannel{GetSettings().robot_account, access_token_, + GetSettings().xmpp_endpoint, task_runner_, network_}); primary_notification_channel_->Start(this); } @@ -833,17 +834,25 @@ bool DeviceRegistrationInfo::UpdateServiceConfig( const std::string& api_key, const std::string& oauth_url, const std::string& service_url, + const std::string& xmpp_endpoint, ErrorPtr* error) { if (HaveRegistrationCredentials()) { return Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered, "Unable to change config for registered device"); } Config::Transaction change{config_}; - change.set_client_id(client_id); - change.set_client_secret(client_secret); - change.set_api_key(api_key); - change.set_oauth_url(oauth_url); - change.set_service_url(service_url); + if (!client_id.empty()) + change.set_client_id(client_id); + if (!client_secret.empty()) + change.set_client_secret(client_secret); + if (!api_key.empty()) + change.set_api_key(api_key); + if (!oauth_url.empty()) + change.set_oauth_url(oauth_url); + if (!service_url.empty()) + change.set_service_url(service_url); + if (!xmpp_endpoint.empty()) + change.set_xmpp_endpoint(xmpp_endpoint); return true; } diff --git a/src/device_registration_info.h b/src/device_registration_info.h index f670b68..a296258 100644 --- a/src/device_registration_info.h +++ b/src/device_registration_info.h @@ -78,6 +78,7 @@ class DeviceRegistrationInfo : public NotificationDelegate, const std::string& api_key, const std::string& oauth_url, const std::string& service_url, + const std::string& xmpp_endpoint, ErrorPtr* error); void GetDeviceInfo(const CloudRequestDoneCallback& callback); diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc index 7908c8b..bbc167e 100644 --- a/src/device_registration_info_unittest.cc +++ b/src/device_registration_info_unittest.cc @@ -44,6 +44,7 @@ namespace { namespace test_data { +const char kXmppEndpoint[] = "xmpp.server.com:1234"; const char kServiceURL[] = "http://gcd.server.com/"; const char kOAuthURL[] = "http://oauth.server.com/"; const char kApiKey[] = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f"; @@ -144,6 +145,7 @@ class DeviceRegistrationInfoTest : public ::testing::Test { settings->model_id = "AAAAA"; settings->oauth_url = test_data::kOAuthURL; settings->service_url = test_data::kServiceURL; + settings->xmpp_endpoint = test_data::kXmppEndpoint; return true; })); config_.reset(new Config{&config_store_}); diff --git a/src/notification/xmpp_channel.cc b/src/notification/xmpp_channel.cc index ceb45ed..f9d7924 100644 --- a/src/notification/xmpp_channel.cc +++ b/src/notification/xmpp_channel.cc @@ -7,6 +7,7 @@ #include <string> #include <base/bind.h> +#include <base/strings/string_number_conversions.h> #include <weave/provider/network.h> #include <weave/provider/task_runner.h> @@ -16,6 +17,7 @@ #include "src/notification/notification_parser.h" #include "src/notification/xml_node.h" #include "src/privet/openssl_utils.h" +#include "src/string_utils.h" #include "src/utils.h" namespace weave { @@ -74,9 +76,6 @@ const BackoffEntry::Policy kDefaultBackoffPolicy = { false, }; -const char kDefaultXmppHost[] = "talk.google.com"; -const uint16_t kDefaultXmppPort = 5223; - // Used for keeping connection alive. const int kRegularPingIntervalSeconds = 60; const int kRegularPingTimeoutSeconds = 30; @@ -91,10 +90,12 @@ const int kConnectingTimeoutAfterNetChangeSeconds = 30; XmppChannel::XmppChannel(const std::string& account, const std::string& access_token, + const std::string& xmpp_endpoint, provider::TaskRunner* task_runner, provider::Network* network) : account_{account}, access_token_{access_token}, + xmpp_endpoint_{xmpp_endpoint}, network_{network}, backoff_entry_{&kDefaultBackoffPolicy}, task_runner_{task_runner}, @@ -285,10 +286,16 @@ void XmppChannel::HandleMessageStanza(std::unique_ptr<XmlNode> stanza) { void XmppChannel::CreateSslSocket() { CHECK(!stream_); state_ = XmppState::kConnecting; - LOG(INFO) << "Starting XMPP connection to " << kDefaultXmppHost << ":" - << kDefaultXmppPort; + LOG(INFO) << "Starting XMPP connection to: " << xmpp_endpoint_; + + std::pair<std::string, std::string> host_port = + SplitAtFirst(xmpp_endpoint_, ":", true); + CHECK(!host_port.first.empty()); + CHECK(!host_port.second.empty()); + uint32_t port = 0; + CHECK(base::StringToUint(host_port.second, &port)) << xmpp_endpoint_; - network_->OpenSslSocket(kDefaultXmppHost, kDefaultXmppPort, + network_->OpenSslSocket(host_port.first, port, base::Bind(&XmppChannel::OnSslSocketReady, task_ptr_factory_.GetWeakPtr())); } diff --git a/src/notification/xmpp_channel.h b/src/notification/xmpp_channel.h index 50e84d2..b0a4468 100644 --- a/src/notification/xmpp_channel.h +++ b/src/notification/xmpp_channel.h @@ -45,6 +45,7 @@ class XmppChannel : public NotificationChannel, // so you will need to reset the XmppClient every time this happens. XmppChannel(const std::string& account, const std::string& access_token, + const std::string& xmpp_endpoint, provider::TaskRunner* task_runner, provider::Network* network); ~XmppChannel() override = default; @@ -124,12 +125,15 @@ class XmppChannel : public NotificationChannel, // Robot account name for the device. std::string account_; - // Full JID of this device. - std::string jid_; - // OAuth access token for the account. Expires fairly frequently. std::string access_token_; + // Xmpp endpoint. + std::string xmpp_endpoint_; + + // Full JID of this device. + std::string jid_; + provider::Network* network_{nullptr}; std::unique_ptr<Stream> stream_; diff --git a/src/notification/xmpp_channel_unittest.cc b/src/notification/xmpp_channel_unittest.cc index 674fe22..dfa2a79 100644 --- a/src/notification/xmpp_channel_unittest.cc +++ b/src/notification/xmpp_channel_unittest.cc @@ -26,6 +26,7 @@ namespace { constexpr char kAccountName[] = "Account@Name"; constexpr char kAccessToken[] = "AccessToken"; +constexpr char kEndpoint[] = "endpoint:456"; constexpr char kStartStreamMessage[] = "<stream:stream to='clouddevices.gserviceaccount.com' " @@ -84,7 +85,8 @@ class FakeXmppChannel : public XmppChannel { public: explicit FakeXmppChannel(provider::TaskRunner* task_runner, provider::Network* network) - : XmppChannel{kAccountName, kAccessToken, task_runner, network}, + : XmppChannel{kAccountName, kAccessToken, kEndpoint, task_runner, + network}, stream_{new test::FakeStream{task_runner_}}, fake_stream_{stream_.get()} {} @@ -122,7 +124,7 @@ class MockNetwork : public provider::test::MockNetwork { class XmppChannelTest : public ::testing::Test { protected: XmppChannelTest() { - EXPECT_CALL(network_, OpenSslSocket("talk.google.com", 5223, _)) + EXPECT_CALL(network_, OpenSslSocket("endpoint", 456, _)) .WillOnce( WithArgs<2>(Invoke(&xmpp_client_, &FakeXmppChannel::Connect))); } diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc index 66d04c4..c82887e 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,9 +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"; +const int kSessionIdTtlMinutes = 1; + +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 <class T> void AppendToArray(T value, std::vector<uint8_t>* array) { @@ -37,78 +48,108 @@ void AppendToArray(T value, std::vector<uint8_t>* 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)); + 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_; } + + protected: + UwMacaroonCaveat caveat_{}; + std::vector<uint8_t> buffer_; + + DISALLOW_COPY_AND_ASSIGN(Caveat); +}; - // TODO(vitalybuka): Use _get_buffer_size_ when available. - Caveat(UwMacaroonCaveatType type, const std::string& value) - : buffer(std::max<size_t>(value.size(), 32u) * 2) { - CHECK(uw_macaroon_caveat_create_with_str_( - type, reinterpret_cast<const uint8_t*>(value.data()), value.size(), - buffer.data(), buffer.size(), &caveat)); +class ScopeCaveat : public Caveat { + public: + explicit ScopeCaveat(UwMacaroonCaveatScopeType scope) + : Caveat(kUwMacaroonCaveatTypeScope, 0) { + CHECK(uw_macaroon_caveat_create_scope_(scope, buffer_.data(), + buffer_.size(), &caveat_)); } - const UwMacaroonCaveat& GetCaveat() const { return caveat; } + DISALLOW_COPY_AND_ASSIGN(ScopeCaveat); +}; - private: - UwMacaroonCaveat caveat; - std::vector<uint8_t> buffer; +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_)); + } - DISALLOW_COPY_AND_ASSIGN(Caveat); + DISALLOW_COPY_AND_ASSIGN(TimestampCaveat); }; -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 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_)); } - if (caveat_type != type) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unexpected caveat type"); + DISALLOW_COPY_AND_ASSIGN(ExpirationCaveat); +}; + +class UserIdCaveat : public Caveat { + public: + explicit UserIdCaveat(const std::vector<uint8_t>& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeUser, id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_user_( + id.data(), id.size(), buffer_.data(), buffer_.size(), &caveat_)); } - return true; -} + DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); +}; -bool ReadCaveat(const UwMacaroonCaveat& caveat, - UwMacaroonCaveatType type, - uint32_t* value, - ErrorPtr* error) { - if (!CheckCaveatType(caveat, type, error)) - return false; +class AppIdCaveat : public Caveat { + public: + explicit AppIdCaveat(const std::vector<uint8_t>& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeApp, id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_app_( + id.data(), id.size(), buffer_.data(), buffer_.size(), &caveat_)); + } - if (!uw_macaroon_caveat_get_value_uint_(&caveat, value)) { - return Error::AddTo(error, FROM_HERE, kInvalidTokenError, - "Unable to read caveat"); + DISALLOW_COPY_AND_ASSIGN(AppIdCaveat); +}; + +class ServiceCaveat : public Caveat { + public: + explicit ServiceCaveat(const std::string& id) + : Caveat(kUwMacaroonCaveatTypeDelegateeService, id.size()) { + CHECK(uw_macaroon_caveat_create_delegatee_service_( + reinterpret_cast<const uint8_t*>(id.data()), id.size(), buffer_.data(), + buffer_.size(), &caveat_)); } - return true; -} + DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); +}; -bool ReadCaveat(const UwMacaroonCaveat& caveat, - UwMacaroonCaveatType type, - std::string* value, - ErrorPtr* error) { - if (!CheckCaveatType(caveat, type, error)) - return false; +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<const uint8_t*>(id.data()), id.size(), 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(SessionIdCaveat); +}; + +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<const char*>(start), size); - return true; -} + DISALLOW_COPY_AND_ASSIGN(ClientAuthTokenCaveat); +}; std::vector<uint8_t> CreateSecret() { std::vector<uint8_t> secret(kSha256OutputSize); @@ -122,18 +163,53 @@ bool IsClaimAllowed(RootClientTokenOwner curret, RootClientTokenOwner claimer) { std::vector<uint8_t> CreateMacaroonToken( const std::vector<uint8_t>& secret, - const std::vector<UwMacaroonCaveat>& caveats) { + const base::Time& time, + const std::vector<const UwMacaroonCaveat*>& 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<uint8_t> token(kMaxMacaroonSize); + std::vector<uint8_t> serialized_token(kMaxMacaroonSize); size_t len = 0; - CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len)); - token.resize(len); + CHECK(uw_macaroon_serialize_(&macaroon, serialized_token.data(), + serialized_token.size(), &len)); + serialized_token.resize(len); - return token; + return serialized_token; +} + +std::vector<uint8_t> ExtendMacaroonToken( + const UwMacaroon& macaroon, + const base::Time& time, + const std::vector<const UwMacaroonCaveat*>& caveats) { + UwMacaroonContext context{}; + CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context)); + + UwMacaroon prev_macaroon = macaroon; + std::vector<uint8_t> prev_buffer(kMaxMacaroonSize); + std::vector<uint8_t> 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<uint8_t> serialized_token(kMaxMacaroonSize); + size_t len = 0; + CHECK(uw_macaroon_serialize_(&prev_macaroon, serialized_token.data(), + serialized_token.size(), &len)); + serialized_token.resize(len); + + return serialized_token; } bool LoadMacaroon(const std::vector<uint8_t>& token, @@ -141,8 +217,8 @@ bool LoadMacaroon(const std::vector<uint8_t>& 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 +227,16 @@ bool LoadMacaroon(const std::vector<uint8_t>& token, bool VerifyMacaroon(const std::vector<uint8_t>& 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,14 +321,22 @@ AuthManager::~AuthManager() {} std::vector<uint8_t> 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<uint32_t>((Now() + ttl).ToTimeT())}; + 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. + std::vector<uint8_t> id_with_type{user_info.id().user}; + id_with_type.push_back(static_cast<uint8_t>(user_info.id().type)); + UserIdCaveat user{id_with_type}; + AppIdCaveat app{user_info.id().app}; + ExpirationCaveat expiration{now + ttl}; return CreateMacaroonToken( - access_secret_, + access_secret_, now, { - scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(), + + &issued.GetCaveat(), &scope.GetCaveat(), &user.GetCaveat(), + &app.GetCaveat(), &expiration.GetCaveat(), }); } @@ -256,37 +346,40 @@ bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token, std::vector<uint8_t> 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)) { + macaroon.num_caveats != 5 || + !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(2u, result.num_delegatees); + CHECK_EQ(kUwMacaroonDelegateeTypeUser, result.delegatees[0].type); + CHECK_EQ(kUwMacaroonDelegateeTypeApp, result.delegatees[1].type); + CHECK_GT(result.delegatees[0].id_len, 1u); + std::vector<uint8_t> 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<AuthType>(user_id.back()); + user_id.pop_back(); + + std::vector<uint8_t> 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; } @@ -309,7 +402,7 @@ std::vector<uint8_t> AuthManager::ClaimRootClientAuthToken( std::unique_ptr<AuthManager>{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<uint8_t>& token, @@ -332,14 +425,20 @@ bool AuthManager::ConfirmClientAuthToken(const std::vector<uint8_t>& token, return true; } -std::vector<uint8_t> AuthManager::GetRootClientAuthToken() const { - Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner}; - Caveat issued{kUwMacaroonCaveatTypeIssued, - static_cast<uint32_t>(Now().ToTimeT())}; - return CreateMacaroonToken(auth_secret_, - { - scope.GetCaveat(), issued.GetCaveat(), - }); +std::vector<uint8_t> AuthManager::GetRootClientAuthToken( + RootClientTokenOwner owner) const { + CHECK(RootClientTokenOwner::kNone != owner); + ClientAuthTokenCaveat auth_token; + const base::Time now = Now(); + TimestampCaveat issued{now}; + + ServiceCaveat client{owner == RootClientTokenOwner::kCloud ? "google.com" + : ""}; + return CreateMacaroonToken( + auth_secret_, now, + { + &auth_token.GetCaveat(), &issued.GetCaveat(), &client.GetCaveat(), + }); } base::Time AuthManager::Now() const { @@ -350,8 +449,9 @@ bool AuthManager::IsValidAuthToken(const std::vector<uint8_t>& token, ErrorPtr* error) const { std::vector<uint8_t> 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 +465,63 @@ 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<uint8_t> 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::kInvalidAuthCode, + "Invalid token data"); + } + + // TODO: Integrate black list checks. + auto delegates_rbegin = std::reverse_iterator<const UwMacaroonDelegateeInfo*>( + result.delegatees + result.num_delegatees); + auto delegates_rend = + std::reverse_iterator<const UwMacaroonDelegateeInfo*>(result.delegatees); + auto last_user_id = + std::find_if(delegates_rbegin, delegates_rend, + [](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, + "User ID is missing"); + } + + const char* session_id = reinterpret_cast<const char*>(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) 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::vector<uint8_t> user_id{last_user_id->id, + last_user_id->id + last_user_id->id_len}; + std::vector<uint8_t> 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}}; - // 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) @@ -388,11 +532,45 @@ bool AuthManager::CreateAccessTokenFromAuth( return true; } -std::vector<uint8_t> AuthManager::CreateSessionId() { - std::vector<uint8_t> 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<uint8_t> AuthManager::DelegateToUser( + const std::vector<uint8_t>& token, + base::TimeDelta ttl, + const UserInfo& user_info) const { + std::vector<uint8_t> 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{user_info.id().user}; + AppIdCaveat app{user_info.id().app}; + SessionIdCaveat session{CreateSessionId()}; + + std::vector<const UwMacaroonCaveat*> 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.h b/src/privet/auth_manager.h index 309d80e..f0a5761 100644 --- a/src/privet/auth_manager.h +++ b/src/privet/auth_manager.h @@ -9,6 +9,7 @@ #include <string> #include <vector> +#include <base/gtest_prod_util.h> #include <base/time/default_clock.h> #include <base/time/time.h> #include <weave/error.h> @@ -54,7 +55,7 @@ class AuthManager { bool ConfirmClientAuthToken(const std::vector<uint8_t>& token, ErrorPtr* error); - std::vector<uint8_t> GetRootClientAuthToken() const; + std::vector<uint8_t> GetRootClientAuthToken(RootClientTokenOwner owner) const; bool IsValidAuthToken(const std::vector<uint8_t>& token, ErrorPtr* error) const; bool CreateAccessTokenFromAuth(const std::vector<uint8_t>& auth_token, @@ -67,13 +68,21 @@ class AuthManager { void SetAuthSecret(const std::vector<uint8_t>& secret, RootClientTokenOwner owner); - std::vector<uint8_t> CreateSessionId(); + std::string CreateSessionId() const; + bool IsValidSessionId(const std::string& session_id) const; private: + friend class AuthManagerTest; + + // Test helpers. Device does not need to implement delegation. + std::vector<uint8_t> DelegateToUser(const std::vector<uint8_t>& 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<uint8_t> auth_secret_; // Persistent. std::vector<uint8_t> certificate_fingerprint_; diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc index 70750ad..294aefa 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; @@ -29,6 +30,11 @@ class AuthManagerTest : public testing::Test { } protected: + std::vector<uint8_t> DelegateToUser(const std::vector<uint8_t>& token, + base::TimeDelta ttl, + const UserInfo& user_info) const { + return auth_.DelegateToUser(token, ttl, user_info); + } const std::vector<uint8_t> 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}; @@ -64,49 +70,90 @@ TEST_F(AuthManagerTest, Constructor) { } TEST_F(AuthManagerTest, CreateAccessToken) { - EXPECT_EQ("UABRUHgcSZDry0bvIsoJv+WDQgEURQJjMjM0RgUaVArkgA==", + EXPECT_EQ("WC2FRggaG52hAEIBFEYJRDIzNABCCkBGBRobnaEAUFAF46oQlMmXgnLstt7wU2w=", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kViewer, "234"}, {}))); - EXPECT_EQ("UL7YEruLg5QQRDIp2+u1cqCDQgEIRQJjMjU3RgUaVArkgA==", + UserInfo{AuthScope::kViewer, TestUserId{"234"}}, {}))); + EXPECT_EQ("WC2FRggaG52hAEIBCEYJRDI1NwBCCkBGBRobnaEAUEdWRNHcu/0mA6c3e0tgDrk=", Base64Encode(auth_.CreateAccessToken( - UserInfo{AuthScope::kManager, "257"}, {}))); - EXPECT_EQ("UPFGeZRanR1wLGYLP5ZDkXiDQgECRQJjNDU2RgUaVArkgA==", + UserInfo{AuthScope::kManager, TestUserId{"257"}}, {}))); + EXPECT_EQ("WC2FRggaG52hAEIBAkYJRDQ1NgBCCkBGBRobnaEAUH2ZLgUPdTtjNRa+PoDkMW4=", 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("UMm9KlF3OEtZFBmhScJpl4uDQgEORQJjMzQ1RgUaVBllAA==", + EXPECT_EQ("WC2FRggaG6whgEIBDkYJRDM0NQBCCkBGBRobrCGAUDAFptj7bbYmbpaa6Wpb1Wo=", 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) { @@ -117,18 +164,24 @@ 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, TestUserId{"234"}}); + EXPECT_FALSE(auth.ParseAccessToken(extended, &user_info, nullptr)); + EXPECT_CALL(clock_, Now()) .WillRepeatedly( Return(kStartTime + base::TimeDelta::FromSeconds(i + 1))); @@ -137,35 +190,135 @@ TEST_F(AuthManagerTest, ParseAccessToken) { } TEST_F(AuthManagerTest, GetRootClientAuthToken) { - EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==", - Base64Encode(auth_.GetRootClientAuthToken())); + EXPECT_EQ("WCCDQxkgAUYIGhudoQBCDEBQZgRhYq78I8GtFUZHNBbfGw==", + Base64Encode( + auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); +} + +TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentOwner) { + EXPECT_EQ( + "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("UBpNF8g/GbNUmAyHg1qqJr+CQgECRgMaVB6rAA==", - Base64Encode(auth_.GetRootClientAuthToken())); + EXPECT_EQ("WCCDQxkgAUYIGhuxZ4BCDEBQjO+OTbjjTzZ/Dvk66nfQqg==", + Base64Encode( + auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient))); } TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) { AuthManager auth{kSecret2, {}, kSecret1, &clock_}; - EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==", - Base64Encode(auth.GetRootClientAuthToken())); + EXPECT_EQ( + "WCCDQxkgAUYIGhudoQBCDEBQ2MZF8YXv5pbtmMxwz9VtLA==", + 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)); } } +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<uint8_t> access_token; + AuthScope scope; + base::TimeDelta ttl; + auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud); + auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000), + UserInfo{AuthScope::kUser, TestUserId{"234"}}); + EXPECT_EQ( + "WE+IQxkgAUYIGhudoQBMDEpnb29nbGUuY29tRggaG52hAEYFGhudpOhCAQ5FCUMyMzRNEUs0" + "NjMzMTUyMDA6MVCRVKU+0SpOoBppnwqdKMwP", + 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(TestUserId{"234"}, user_info.id()); +} + +TEST_F(AuthManagerTest, CreateAccessTokenFromAuthNotMinted) { + std::vector<uint8_t> 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, TestUserId{"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, TestUserId{"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, TestUserId{"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); } @@ -241,18 +394,5 @@ TEST_F(AuthManagerClaimTest, TokenOverflow) { EXPECT_FALSE(auth_.ConfirmClientAuthToken(token, nullptr)); } -TEST_F(AuthManagerClaimTest, CreateAccessTokenFromAuth) { - std::vector<uint8_t> access_token; - AuthScope scope; - base::TimeDelta ttl; - EXPECT_TRUE(auth_.CreateAccessTokenFromAuth( - auth_.GetRootClientAuthToken(), 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()); -} - } // namespace privet } // namespace weave 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<std::string, std::string> command_owners_; + std::map<std::string, UserAppId> 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<PairingType>{ 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<uint8_t> HmacSha256(const std::vector<uint8_t>& key, const std::vector<uint8_t>& data) { std::vector<uint8_t> 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/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<uint8_t>& user_id, + const std::vector<uint8_t>& 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<uint8_t> user; + std::vector<uint8_t> 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 0f00699..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<uint8_t>* access_token, AuthScope* access_token_scope, base::TimeDelta* access_token_ttl) { - UserInfo user_info{desired_scope, - std::to_string(static_cast<int>(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); @@ -388,7 +389,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( 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()); } } diff --git a/third_party/get_libevhtp.sh b/third_party/get_libevhtp.sh index c270813..cfcada9 100755 --- a/third_party/get_libevhtp.sh +++ b/third_party/get_libevhtp.sh @@ -9,17 +9,19 @@ cd $(dirname "$0") THIRD_PARTY=$(pwd) +LIBEVHTP_VERSION=1.2.11n + mkdir -p include lib rm -rf $THIRD_PARTY/libevhtp -curl -L https://github.com/ellzey/libevhtp/archive/1.2.10.tar.gz | tar xz || exit 1 -mv libevhtp-1.2.10 $THIRD_PARTY/libevhtp || exit 1 +curl -L https://github.com/ellzey/libevhtp/archive/$LIBEVHTP_VERSION.tar.gz | tar xz || exit 1 +mv libevhtp-$LIBEVHTP_VERSION $THIRD_PARTY/libevhtp || exit 1 cd $THIRD_PARTY/libevhtp || exit 1 cmake -D EVHTP_DISABLE_REGEX:BOOL=ON . || exit 1 make evhtp || exit 1 -cp -rf evhtp-config.h evhtp.h evthr/evthr.h htparse/htparse.h $THIRD_PARTY/include/ || exit 1 +cp -rf *.h $THIRD_PARTY/include/ || exit 1 cp -f libevhtp.a $THIRD_PARTY/lib/ || exit 1 rm -rf $THIRD_PARTY/libevhtp 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 <openssl/evp.h> #include <openssl/hmac.h> -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); } 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 <stddef.h> #include <stdint.h> -// 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..c823804 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,231 @@ 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 = (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) { + *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, + 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; + } + } + + result->granted_scope = get_closest_scope(result->granted_scope); + 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..c739bca 100644 --- a/third_party/libuweave/src/macaroon.h +++ b/third_party/libuweave/src/macaroon.h @@ -9,7 +9,8 @@ #include <stddef.h> #include <stdint.h> -#include "macaroon_caveat.h" +#include "src/macaroon_caveat.h" +#include "src/macaroon_context.h" #define UW_MACAROON_MAC_LEN 16 @@ -20,45 +21,80 @@ 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 enum { + kUwMacaroonDelegateeTypeNone = 0, + kUwMacaroonDelegateeTypeUser = 1, + kUwMacaroonDelegateeTypeApp = 2, + kUwMacaroonDelegateeTypeService = 3, +} UwMacaroonDelegateeType; -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 { + const uint8_t* id; + size_t id_len; + UwMacaroonDelegateeType type; + uint32_t timestamp; +} UwMacaroonDelegateeInfo; + +#define MAX_NUM_DELEGATEES 10 -bool uw_macaroon_verify_(const UwMacaroon* macaroon, - const uint8_t* root_key, - size_t root_key_len); +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; -// Create a new macaroon with a new caveat +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); + +/** 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. 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, + 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..dc4ee3b 100644 --- a/third_party/libuweave/src/macaroon_caveat.c +++ b/third_party/libuweave/src/macaroon_caveat.c @@ -3,125 +3,298 @@ // found in the LICENSE file. #include "src/macaroon_caveat.h" +#include "src/macaroon_caveat_internal.h" #include <string.h> #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 +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 kUwMacaroonCaveatTypeDelegateeService: + case kUwMacaroonCaveatTypeBleSessionID: + case kUwMacaroonCaveatTypeLanSessionID: + case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: + case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: + return true; + } + return false; +} -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_scope_type_(UwMacaroonCaveatScopeType type) { + switch (type) { + case kUwMacaroonCaveatScopeTypeOwner: + case kUwMacaroonCaveatScopeTypeManager: + case kUwMacaroonCaveatScopeTypeUser: + case kUwMacaroonCaveatScopeTypeViewer: + return true; } + return false; +} - caveat->bytes = buffer; - size_t encoded_str_len, total_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; + } - uint32_t unsigned_int = (uint32_t)type; - if (!uw_macaroon_encoding_encode_uint_(unsigned_int, buffer, buffer_size, + 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; - 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; + total_str_len += encoded_str_len; - 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; + new_caveat->bytes = buffer; + new_caveat->num_bytes = total_str_len; + return true; +} - default: - // Should never reach here - return false; +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; } + 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; - 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_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 != 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_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, 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) { - return false; - } - if (type != kUwMacaroonCaveatTypeScope && - type != kUwMacaroonCaveatTypeIssued && - type != kUwMacaroonCaveatTypeTTL && - type != kUwMacaroonCaveatTypeExpiration) { - 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 kUwMacaroonCaveatTypeDelegateeService: + case kUwMacaroonCaveatTypeLanSessionID: + case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: + case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: + return str_len + UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; + + default: + return 0; // For errors } +} - return create_caveat_(type, &value, sizeof(uint32_t), buffer, buffer_size, - caveat); +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_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)) { +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; } - if (type != kUwMacaroonCaveatTypeIdentifier) { - return false; + + 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_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) { + 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); +} - return create_caveat_(type, str, str_len, buffer, buffer_size, 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 +310,226 @@ 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 === */ + +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; + } - if (*type != kUwMacaroonCaveatTypeStop && - *type != kUwMacaroonCaveatTypeScope && - *type != kUwMacaroonCaveatTypeIdentifier && - *type != kUwMacaroonCaveatTypeIssued && - *type != kUwMacaroonCaveatTypeTTL && - *type != kUwMacaroonCaveatTypeExpiration && - *type != kUwMacaroonCaveatTypeSessionIdentifier) { + // 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, + uint32_t issued_time, + UwMacaroonValidationResult* result) { + if (result->num_delegatees >= MAX_NUM_DELEGATEES || issued_time == 0) { + return false; + } + + 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 or DelegateeService + if (result->delegatees[i].type == delegatee_type) { + 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].type = delegatee_type; + result->delegatees[result->num_delegatees].timestamp = issued_time; + 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: + 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->issued_time == 0) { + return false; + } + return update_and_check_expiration_time( + context->current_time, state->issued_time + 60 * 60, result); + + case kUwMacaroonCaveatTypeTTL24Hour: + if (state->issued_time == 0) { + 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: + case kUwMacaroonCaveatTypeDelegateeApp: + case kUwMacaroonCaveatTypeDelegateeService: + return update_delegatee_list(caveat_type, caveat, state->issued_time, + 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_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 +541,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 +558,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 +569,13 @@ 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 != kUwMacaroonCaveatTypeDelegateeService && + type != kUwMacaroonCaveatTypeLanSessionID && + type != kUwMacaroonCaveatTypeClientAuthorizationTokenV1 && + type != kUwMacaroonCaveatTypeServerAuthenticationTokenV1) { // Wrong type return false; } @@ -200,48 +586,16 @@ 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_()) { - 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)) { +bool uw_macaroon_caveat_init_validation_state_( + UwMacaroonValidationState* state) { + if (state == NULL) { 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->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..4905667 100644 --- a/third_party/libuweave/src/macaroon_caveat.h +++ b/third_party/libuweave/src/macaroon_caveat.h @@ -15,13 +15,22 @@ 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 + kUwMacaroonCaveatTypeDelegateeService = 12, // bstr + + kUwMacaroonCaveatTypeAppCommandsOnly = 11, // no value + kUwMacaroonCaveatTypeBleSessionID = 16, // no value + kUwMacaroonCaveatTypeLanSessionID = 17, // bstr + kUwMacaroonCaveatTypeClientAuthorizationTokenV1 = 8193, // bstr (0x2001) + kUwMacaroonCaveatTypeServerAuthenticationTokenV1 = 12289, // bstr (0x3001) } UwMacaroonCaveatType; typedef enum { @@ -31,28 +40,83 @@ typedef enum { kUwMacaroonCaveatScopeTypeViewer = 20, } UwMacaroonCaveatScopeType; -bool uw_macaroon_caveat_create_without_value_(UwMacaroonCaveatType type, +// 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); + +// 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_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); +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..d6e7b07 --- /dev/null +++ b/third_party/libuweave/src/macaroon_caveat_internal.h @@ -0,0 +1,40 @@ +// 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 <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#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 { + uint32_t issued_time; // 0 when invalid or not set. +} 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 <stdbool.h> #include <stddef.h> @@ -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 <stddef.h> #include <stdint.h> -// 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); |