| // 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. | |
| #include "src/privet/auth_manager.h" | |
| #include <algorithm> | |
| #include <base/guid.h> | |
| #include <base/rand_util.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include "src/config.h" | |
| #include "src/data_encoding.h" | |
| #include "src/privet/constants.h" | |
| #include "src/privet/openssl_utils.h" | |
| #include "src/string_utils.h" | |
| extern "C" { | |
| #include "third_party/libuweave/src/macaroon.h" | |
| #include "third_party/libuweave/src/macaroon_caveat_internal.h" | |
| } | |
| namespace weave { | |
| 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) { | |
| auto begin = reinterpret_cast<const uint8_t*>(&value); | |
| array->insert(array->end(), begin, begin + sizeof(value)); | |
| } | |
| class Caveat { | |
| public: | |
| 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); | |
| }; | |
| class ScopeCaveat : public Caveat { | |
| public: | |
| explicit ScopeCaveat(UwMacaroonCaveatScopeType scope) | |
| : Caveat(kUwMacaroonCaveatTypeScope, 0) { | |
| CHECK(uw_macaroon_caveat_create_scope_(scope, buffer_.data(), | |
| buffer_.size(), &caveat_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(ScopeCaveat); | |
| }; | |
| class TimestampCaveat : public Caveat { | |
| public: | |
| explicit TimestampCaveat(const base::Time& timestamp) | |
| : Caveat(kUwMacaroonCaveatTypeDelegationTimestamp, 0) { | |
| CHECK(uw_macaroon_caveat_create_delegation_timestamp_( | |
| ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(TimestampCaveat); | |
| }; | |
| class ExpirationCaveat : public Caveat { | |
| public: | |
| explicit ExpirationCaveat(const base::Time& timestamp) | |
| : Caveat(kUwMacaroonCaveatTypeExpirationAbsolute, 0) { | |
| CHECK(uw_macaroon_caveat_create_expiration_absolute_( | |
| ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_)); | |
| } | |
| 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_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(UserIdCaveat); | |
| }; | |
| 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_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(AppIdCaveat); | |
| }; | |
| class ServiceCaveat : public Caveat { | |
| public: | |
| explicit ServiceCaveat(UwMacaroonCaveatCloudServiceId service_id) | |
| : Caveat(kUwMacaroonCaveatTypeDelegateeService, 0) { | |
| CHECK(uw_macaroon_caveat_create_delegatee_service_( | |
| service_id, buffer_.data(), buffer_.size(), &caveat_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(ServiceCaveat); | |
| }; | |
| class SessionIdCaveat : public Caveat { | |
| public: | |
| explicit SessionIdCaveat(const std::string& id) | |
| : Caveat(kUwMacaroonCaveatTypeLanSessionID, id.size()) { | |
| CHECK(uw_macaroon_caveat_create_lan_session_id_( | |
| reinterpret_cast<const uint8_t*>(id.data()), id.size(), buffer_.data(), | |
| buffer_.size(), &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_)); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(ClientAuthTokenCaveat); | |
| }; | |
| std::vector<uint8_t> CreateSecret() { | |
| std::vector<uint8_t> secret(kSha256OutputSize); | |
| base::RandBytes(secret.data(), secret.size()); | |
| return secret; | |
| } | |
| bool IsClaimAllowed(RootClientTokenOwner curret, RootClientTokenOwner claimer) { | |
| return claimer > curret || claimer == RootClientTokenOwner::kCloud; | |
| } | |
| std::vector<uint8_t> CreateMacaroonToken( | |
| const std::vector<uint8_t>& secret, | |
| 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, nullptr, 0, | |
| &context)); | |
| UwMacaroon macaroon{}; | |
| CHECK(uw_macaroon_create_from_root_key_(&macaroon, secret.data(), | |
| secret.size(), &context, | |
| caveats.data(), caveats.size())); | |
| std::vector<uint8_t> serialized_token(kMaxMacaroonSize); | |
| size_t len = 0; | |
| CHECK(uw_macaroon_serialize_(&macaroon, serialized_token.data(), | |
| serialized_token.size(), &len)); | |
| serialized_token.resize(len); | |
| return serialized_token; | |
| } | |
| std::vector<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, 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, | |
| std::vector<uint8_t>* buffer, | |
| UwMacaroon* macaroon, | |
| ErrorPtr* error) { | |
| buffer->resize(kMaxMacaroonSize); | |
| if (!uw_macaroon_deserialize_(token.data(), token.size(), buffer->data(), | |
| buffer->size(), macaroon)) { | |
| return Error::AddTo(error, FROM_HERE, kInvalidTokenError, | |
| "Invalid token format"); | |
| } | |
| return true; | |
| } | |
| bool VerifyMacaroon(const std::vector<uint8_t>& secret, | |
| const UwMacaroon& macaroon, | |
| const base::Time& time, | |
| UwMacaroonValidationResult* result, | |
| ErrorPtr* error) { | |
| CHECK_EQ(kSha256OutputSize, secret.size()); | |
| UwMacaroonContext context = {}; | |
| CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, 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; | |
| } | |
| UwMacaroonCaveatScopeType ToMacaroonScope(AuthScope scope) { | |
| switch (scope) { | |
| case AuthScope::kViewer: | |
| return kUwMacaroonCaveatScopeTypeViewer; | |
| case AuthScope::kUser: | |
| return kUwMacaroonCaveatScopeTypeUser; | |
| case AuthScope::kManager: | |
| return kUwMacaroonCaveatScopeTypeManager; | |
| case AuthScope::kOwner: | |
| return kUwMacaroonCaveatScopeTypeOwner; | |
| default: | |
| NOTREACHED() << EnumToString(scope); | |
| } | |
| return kUwMacaroonCaveatScopeTypeViewer; | |
| } | |
| AuthScope FromMacaroonScope(uint32_t scope) { | |
| if (scope <= kUwMacaroonCaveatScopeTypeOwner) | |
| return AuthScope::kOwner; | |
| if (scope <= kUwMacaroonCaveatScopeTypeManager) | |
| return AuthScope::kManager; | |
| if (scope <= kUwMacaroonCaveatScopeTypeUser) | |
| return AuthScope::kUser; | |
| if (scope <= kUwMacaroonCaveatScopeTypeViewer) | |
| return AuthScope::kViewer; | |
| return AuthScope::kNone; | |
| } | |
| } // namespace | |
| AuthManager::AuthManager(Config* config, | |
| const std::vector<uint8_t>& certificate_fingerprint) | |
| : config_{config}, | |
| certificate_fingerprint_{certificate_fingerprint}, | |
| access_secret_{CreateSecret()} { | |
| if (config_) { | |
| SetAuthSecret(config_->GetSettings().secret, | |
| config_->GetSettings().root_client_token_owner); | |
| } else { | |
| SetAuthSecret({}, RootClientTokenOwner::kNone); | |
| } | |
| } | |
| AuthManager::AuthManager(const std::vector<uint8_t>& auth_secret, | |
| const std::vector<uint8_t>& certificate_fingerprint, | |
| const std::vector<uint8_t>& access_secret, | |
| base::Clock* clock) | |
| : AuthManager(nullptr, certificate_fingerprint) { | |
| access_secret_ = access_secret.size() == kSha256OutputSize ? access_secret | |
| : CreateSecret(); | |
| SetAuthSecret(auth_secret, RootClientTokenOwner::kNone); | |
| if (clock) | |
| clock_ = clock; | |
| } | |
| void AuthManager::SetAuthSecret(const std::vector<uint8_t>& secret, | |
| RootClientTokenOwner owner) { | |
| auth_secret_ = secret; | |
| if (auth_secret_.size() != kSha256OutputSize) { | |
| auth_secret_ = CreateSecret(); | |
| owner = RootClientTokenOwner::kNone; | |
| } | |
| if (!config_ || (config_->GetSettings().secret == auth_secret_ && | |
| config_->GetSettings().root_client_token_owner == owner)) { | |
| return; | |
| } | |
| Config::Transaction change{config_}; | |
| change.set_secret(secret); | |
| change.set_root_client_token_owner(owner); | |
| change.Commit(); | |
| } | |
| AuthManager::~AuthManager() {} | |
| std::vector<uint8_t> AuthManager::CreateAccessToken(const UserInfo& user_info, | |
| base::TimeDelta ttl) const { | |
| const base::Time now = Now(); | |
| TimestampCaveat issued{now}; | |
| ScopeCaveat scope{ToMacaroonScope(user_info.scope())}; | |
| // Macaroons have no caveats for auth type. So we just append the type to the | |
| // user ID. | |
| 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_, now, | |
| { | |
| &issued.GetCaveat(), &scope.GetCaveat(), &user.GetCaveat(), | |
| &app.GetCaveat(), &expiration.GetCaveat(), | |
| }); | |
| } | |
| bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token, | |
| UserInfo* user_info, | |
| ErrorPtr* error) const { | |
| std::vector<uint8_t> buffer; | |
| UwMacaroon macaroon{}; | |
| UwMacaroonValidationResult result{}; | |
| const base::Time now = Now(); | |
| if (!LoadMacaroon(token, &buffer, &macaroon, 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(result.granted_scope)}; | |
| if (auth_scope == AuthScope::kNone) { | |
| return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization, | |
| "Invalid token data"); | |
| } | |
| // 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, UserAppId{type, user_id, app_id}}; | |
| return true; | |
| } | |
| std::vector<uint8_t> AuthManager::ClaimRootClientAuthToken( | |
| RootClientTokenOwner owner, | |
| ErrorPtr* error) { | |
| CHECK(RootClientTokenOwner::kNone != owner); | |
| if (config_) { | |
| auto current = config_->GetSettings().root_client_token_owner; | |
| if (!IsClaimAllowed(current, owner)) { | |
| Error::AddToPrintf(error, FROM_HERE, errors::kAlreadyClaimed, | |
| "Device already claimed by '%s'", | |
| EnumToString(current).c_str()); | |
| return {}; | |
| } | |
| }; | |
| pending_claims_.push_back(std::make_pair( | |
| std::unique_ptr<AuthManager>{new AuthManager{nullptr, {}}}, owner)); | |
| if (pending_claims_.size() > kMaxPendingClaims) | |
| pending_claims_.pop_front(); | |
| return pending_claims_.back().first->GetRootClientAuthToken(owner); | |
| } | |
| bool AuthManager::ConfirmClientAuthToken(const std::vector<uint8_t>& token, | |
| ErrorPtr* error) { | |
| // Cover case when caller sent confirm twice. | |
| if (pending_claims_.empty()) | |
| return IsValidAuthToken(token, error); | |
| auto claim = | |
| std::find_if(pending_claims_.begin(), pending_claims_.end(), | |
| [&token](const decltype(pending_claims_)::value_type& auth) { | |
| return auth.first->IsValidAuthToken(token, nullptr); | |
| }); | |
| if (claim == pending_claims_.end()) { | |
| return Error::AddTo(error, FROM_HERE, errors::kNotFound, "Unknown claim"); | |
| } | |
| SetAuthSecret(claim->first->GetAuthSecret(), claim->second); | |
| pending_claims_.clear(); | |
| return true; | |
| } | |
| 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 | |
| ? kUwMacaroonCaveatCloudServiceIdGoogleWeave | |
| : kUwMacaroonCaveatCloudServiceIdNotCloudRegistered}; | |
| return CreateMacaroonToken( | |
| auth_secret_, now, | |
| { | |
| &auth_token.GetCaveat(), &issued.GetCaveat(), &client.GetCaveat(), | |
| }); | |
| } | |
| base::Time AuthManager::Now() const { | |
| return clock_->Now(); | |
| } | |
| 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, Now(), &result, error)) { | |
| return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode, | |
| "Invalid token"); | |
| } | |
| return true; | |
| } | |
| bool AuthManager::CreateAccessTokenFromAuth( | |
| const std::vector<uint8_t>& auth_token, | |
| base::TimeDelta ttl, | |
| std::vector<uint8_t>* access_token, | |
| AuthScope* access_token_scope, | |
| base::TimeDelta* access_token_ttl, | |
| ErrorPtr* error) const { | |
| 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; | |
| 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}}; | |
| ttl = std::min(ttl, FromJ2000Time(result.expiration_time) - now); | |
| *access_token = CreateAccessToken(info, ttl); | |
| if (access_token_scope) | |
| *access_token_scope = info.scope(); | |
| if (access_token_ttl) | |
| *access_token_ttl = ttl; | |
| return true; | |
| } | |
| 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 | |
| } // namespace weave |