blob: 66d04c4bf56785ee67e4a96e1891aa23821e9005 [file] [log] [blame]
// 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"
}
namespace weave {
namespace privet {
namespace {
const size_t kMaxMacaroonSize = 1024;
const size_t kMaxPendingClaims = 10;
const char kInvalidTokenError[] = "invalid_token";
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:
// TODO(vitalybuka): Use _get_buffer_size_ when available.
Caveat(UwMacaroonCaveatType type, uint32_t value) : buffer(8) {
CHECK(uw_macaroon_caveat_create_with_uint_(type, value, buffer.data(),
buffer.size(), &caveat));
}
// TODO(vitalybuka): Use _get_buffer_size_ when available.
Caveat(UwMacaroonCaveatType type, const std::string& value)
: buffer(std::max<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));
}
const UwMacaroonCaveat& GetCaveat() const { return caveat; }
private:
UwMacaroonCaveat caveat;
std::vector<uint8_t> buffer;
DISALLOW_COPY_AND_ASSIGN(Caveat);
};
bool CheckCaveatType(const UwMacaroonCaveat& caveat,
UwMacaroonCaveatType type,
ErrorPtr* error) {
UwMacaroonCaveatType caveat_type{};
if (!uw_macaroon_caveat_get_type_(&caveat, &caveat_type)) {
return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
"Unable to get type");
}
if (caveat_type != type) {
return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
"Unexpected caveat type");
}
return true;
}
bool ReadCaveat(const UwMacaroonCaveat& caveat,
UwMacaroonCaveatType type,
uint32_t* value,
ErrorPtr* error) {
if (!CheckCaveatType(caveat, type, error))
return false;
if (!uw_macaroon_caveat_get_value_uint_(&caveat, value)) {
return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
"Unable to read caveat");
}
return true;
}
bool ReadCaveat(const UwMacaroonCaveat& caveat,
UwMacaroonCaveatType type,
std::string* value,
ErrorPtr* error) {
if (!CheckCaveatType(caveat, type, error))
return false;
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");
}
value->assign(reinterpret_cast<const char*>(start), size);
return true;
}
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 std::vector<UwMacaroonCaveat>& caveats) {
CHECK_EQ(kSha256OutputSize, secret.size());
UwMacaroon macaroon{};
CHECK(uw_macaroon_new_from_root_key_(&macaroon, secret.data(), secret.size(),
caveats.data(), caveats.size()));
std::vector<uint8_t> token(kMaxMacaroonSize);
size_t len = 0;
CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len));
token.resize(len);
return token;
}
bool LoadMacaroon(const std::vector<uint8_t>& token,
std::vector<uint8_t>* buffer,
UwMacaroon* macaroon,
ErrorPtr* error) {
buffer->resize(kMaxMacaroonSize);
if (!uw_macaroon_load_(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,
ErrorPtr* error) {
CHECK_EQ(kSha256OutputSize, secret.size());
if (!uw_macaroon_verify_(&macaroon, secret.data(), secret.size())) {
return Error::AddTo(error, FROM_HERE, "invalid_signature",
"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 {
Caveat scope{kUwMacaroonCaveatTypeScope, ToMacaroonScope(user_info.scope())};
Caveat user{kUwMacaroonCaveatTypeIdentifier, user_info.user_id()};
Caveat issued{kUwMacaroonCaveatTypeExpiration,
static_cast<uint32_t>((Now() + ttl).ToTimeT())};
return CreateMacaroonToken(
access_secret_,
{
scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(),
});
}
bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token,
UserInfo* user_info,
ErrorPtr* error) const {
std::vector<uint8_t> buffer;
UwMacaroon macaroon{};
uint32_t scope{0};
std::string user_id;
uint32_t expiration{0};
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)) {
return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
"Invalid token");
}
AuthScope auth_scope{FromMacaroonScope(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 (user_info)
*user_info = UserInfo{auth_scope, user_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();
}
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() const {
Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner};
Caveat issued{kUwMacaroonCaveatTypeIssued,
static_cast<uint32_t>(Now().ToTimeT())};
return CreateMacaroonToken(auth_secret_,
{
scope.GetCaveat(), issued.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{};
if (!LoadMacaroon(token, &buffer, &macaroon, error) ||
!VerifyMacaroon(auth_secret_, macaroon, 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 {
// TODO(vitalybuka): implement token validation.
if (!IsValidAuthToken(auth_token, error))
return false;
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()};
// TODO(vitalybuka): TTL also should be reduced in accordance with auth_token.
*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::vector<uint8_t> AuthManager::CreateSessionId() {
std::vector<uint8_t> result;
AppendToArray(Now().ToTimeT(), &result);
AppendToArray(++session_counter_, &result);
return result;
}
} // namespace privet
} // namespace weave