blob: 157c8d69ac8ae381cda62308657d23894c5c5ff5 [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/security_manager.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <base/bind.h>
#include <base/guid.h>
#include <base/logging.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <weave/provider/task_runner.h>
#include "src/data_encoding.h"
#include "src/privet/auth_manager.h"
#include "src/privet/constants.h"
#include "src/privet/openssl_utils.h"
#include "src/string_utils.h"
#include "third_party/chromium/crypto/p224_spake.h"
namespace weave {
namespace privet {
namespace {
const int kSessionExpirationTimeMinutes = 5;
const int kPairingExpirationTimeMinutes = 5;
const int kMaxAllowedPairingAttemts = 3;
const int kPairingBlockingTimeMinutes = 1;
const int kAccessTokenExpirationSeconds = 3600;
class Spakep224Exchanger : public SecurityManager::KeyExchanger {
public:
explicit Spakep224Exchanger(const std::string& password)
: spake_(crypto::P224EncryptedKeyExchange::kPeerTypeServer, password) {}
~Spakep224Exchanger() override = default;
// SecurityManager::KeyExchanger methods.
const std::string& GetMessage() override { return spake_.GetNextMessage(); }
bool ProcessMessage(const std::string& message, ErrorPtr* error) override {
switch (spake_.ProcessMessage(message)) {
case crypto::P224EncryptedKeyExchange::kResultPending:
return true;
case crypto::P224EncryptedKeyExchange::kResultFailed:
return Error::AddTo(error, FROM_HERE, errors::kInvalidClientCommitment,
spake_.error());
default:
LOG(FATAL) << "SecurityManager uses only one round trip";
}
return false;
}
const std::string& GetKey() const override {
return spake_.GetUnverifiedKey();
}
private:
crypto::P224EncryptedKeyExchange spake_;
};
} // namespace
SecurityManager::SecurityManager(const Config* config,
AuthManager* auth_manager,
provider::TaskRunner* task_runner)
: config_{config}, auth_manager_{auth_manager}, task_runner_{task_runner} {
CHECK(auth_manager_);
CHECK_EQ(GetSettings().embedded_code.empty(),
std::find(GetSettings().pairing_modes.begin(),
GetSettings().pairing_modes.end(),
PairingType::kEmbeddedCode) ==
GetSettings().pairing_modes.end());
}
SecurityManager::~SecurityManager() {
while (!pending_sessions_.empty())
ClosePendingSession(pending_sessions_.begin()->first);
}
bool SecurityManager::CreateAccessTokenImpl(AuthType auth_type,
AuthScope desired_scope,
std::vector<uint8_t>* access_token,
AuthScope* access_token_scope,
base::TimeDelta* access_token_ttl) {
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);
if (access_token)
*access_token = auth_manager_->CreateAccessToken(user_info, kTtl);
if (access_token_scope)
*access_token_scope = user_info.scope();
if (access_token_ttl)
*access_token_ttl = kTtl;
return true;
}
bool SecurityManager::CreateAccessTokenImpl(
AuthType auth_type,
const std::vector<uint8_t>& auth_code,
AuthScope desired_scope,
std::vector<uint8_t>* access_token,
AuthScope* access_token_scope,
base::TimeDelta* access_token_ttl,
ErrorPtr* error) {
auto disabled_mode = [](ErrorPtr* error) {
return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthMode,
"Mode is not available");
};
switch (auth_type) {
case AuthType::kAnonymous:
if (!IsAnonymousAuthSupported())
return disabled_mode(error);
return CreateAccessTokenImpl(auth_type, desired_scope, access_token,
access_token_scope, access_token_ttl);
case AuthType::kPairing:
if (!IsPairingAuthSupported())
return disabled_mode(error);
if (!IsValidPairingCode(auth_code)) {
return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
"Invalid authCode");
}
return CreateAccessTokenImpl(auth_type, desired_scope, access_token,
access_token_scope, access_token_ttl);
case AuthType::kLocal:
if (!IsLocalAuthSupported())
return disabled_mode(error);
const base::TimeDelta kTtl =
base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
bool result = auth_manager_->CreateAccessTokenFromAuth(
auth_code, kTtl, access_token, access_token_scope, access_token_ttl,
error);
*access_token_scope = std::min(*access_token_scope, desired_scope);
return result;
}
return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthMode,
"Unsupported auth mode");
}
bool SecurityManager::CreateAccessToken(AuthType auth_type,
const std::string& auth_code,
AuthScope desired_scope,
std::string* access_token,
AuthScope* access_token_scope,
base::TimeDelta* access_token_ttl,
ErrorPtr* error) {
std::vector<uint8_t> auth_decoded;
if (auth_type != AuthType::kAnonymous &&
!Base64Decode(auth_code, &auth_decoded)) {
Error::AddToPrintf(error, FROM_HERE, errors::kInvalidAuthorization,
"Invalid auth_code encoding: %s", auth_code.c_str());
return false;
}
std::vector<uint8_t> access_token_decoded;
if (!CreateAccessTokenImpl(auth_type, auth_decoded, desired_scope,
&access_token_decoded, access_token_scope,
access_token_ttl, error)) {
return false;
}
if (access_token)
*access_token = Base64Encode(access_token_decoded);
return true;
}
bool SecurityManager::ParseAccessToken(const std::string& token,
UserInfo* user_info,
ErrorPtr* error) const {
std::vector<uint8_t> decoded;
if (!Base64Decode(token, &decoded)) {
Error::AddToPrintf(error, FROM_HERE, errors::kInvalidAuthorization,
"Invalid token encoding: %s", token.c_str());
return false;
}
return auth_manager_->ParseAccessToken(decoded, user_info, error);
}
std::set<PairingType> SecurityManager::GetPairingTypes() const {
return GetSettings().pairing_modes;
}
std::set<CryptoType> SecurityManager::GetCryptoTypes() const {
std::set<CryptoType> result{CryptoType::kSpake_p224};
return result;
}
std::set<AuthType> SecurityManager::GetAuthTypes() const {
std::set<AuthType> result;
if (IsAnonymousAuthSupported())
result.insert(AuthType::kAnonymous);
if (IsPairingAuthSupported())
result.insert(AuthType::kPairing);
if (IsLocalAuthSupported())
result.insert(AuthType::kLocal);
return result;
}
std::string SecurityManager::ClaimRootClientAuthToken(ErrorPtr* error) {
return Base64Encode(auth_manager_->ClaimRootClientAuthToken(
RootClientTokenOwner::kClient, error));
}
bool SecurityManager::ConfirmClientAuthToken(const std::string& token,
ErrorPtr* error) {
std::vector<uint8_t> token_decoded;
if (!Base64Decode(token, &token_decoded)) {
Error::AddToPrintf(error, FROM_HERE, errors::kInvalidFormat,
"Invalid auth token string: '%s'", token.c_str());
return false;
}
return auth_manager_->ConfirmClientAuthToken(token_decoded, error);
}
const Config::Settings& SecurityManager::GetSettings() const {
return config_->GetSettings();
}
bool SecurityManager::IsValidPairingCode(
const std::vector<uint8_t>& auth_code) const {
for (const auto& session : confirmed_sessions_) {
const std::string& key = session.second->GetKey();
const std::string& id = session.first;
if (auth_code == HmacSha256(std::vector<uint8_t>(key.begin(), key.end()),
std::vector<uint8_t>(id.begin(), id.end()))) {
pairing_attemts_ = 0;
block_pairing_until_ = base::Time{};
return true;
}
}
LOG(ERROR) << "Attempt to authenticate with invalid code.";
return false;
}
bool SecurityManager::StartPairing(PairingType mode,
CryptoType crypto,
std::string* session_id,
std::string* device_commitment,
ErrorPtr* error) {
if (!CheckIfPairingAllowed(error))
return false;
const auto& pairing_modes = GetSettings().pairing_modes;
if (std::find(pairing_modes.begin(), pairing_modes.end(), mode) ==
pairing_modes.end()) {
return Error::AddTo(error, FROM_HERE, errors::kInvalidParams,
"Pairing mode is not enabled");
}
std::string code;
switch (mode) {
case PairingType::kEmbeddedCode:
CHECK(!GetSettings().embedded_code.empty());
code = GetSettings().embedded_code;
break;
case PairingType::kPinCode:
code = base::StringPrintf("%04i", base::RandInt(0, 9999));
break;
default:
return Error::AddTo(error, FROM_HERE, errors::kInvalidParams,
"Unsupported pairing mode");
}
std::unique_ptr<KeyExchanger> spake;
switch (crypto) {
case CryptoType::kSpake_p224:
spake.reset(new Spakep224Exchanger(code));
break;
// Fall through...
default:
return Error::AddTo(error, FROM_HERE, errors::kInvalidParams,
"Unsupported crypto");
}
// Allow only a single session at a time for now.
while (!pending_sessions_.empty())
ClosePendingSession(pending_sessions_.begin()->first);
std::string session;
do {
session = base::GenerateGUID();
} while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
pending_sessions_.find(session) != pending_sessions_.end());
std::string commitment = spake->GetMessage();
pending_sessions_.insert(std::make_pair(session, std::move(spake)));
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SecurityManager::ClosePendingSession),
weak_ptr_factory_.GetWeakPtr(), session),
base::TimeDelta::FromMinutes(kPairingExpirationTimeMinutes));
*session_id = session;
*device_commitment = Base64Encode(commitment);
LOG(INFO) << "Pairing code for session " << *session_id << " is " << code;
// TODO(vitalybuka): Handle case when device can't start multiple pairing
// simultaneously and implement throttling to avoid brute force attack.
if (!on_start_.is_null()) {
on_start_.Run(session, mode,
std::vector<uint8_t>{code.begin(), code.end()});
}
return true;
}
bool SecurityManager::ConfirmPairing(const std::string& session_id,
const std::string& client_commitment,
std::string* fingerprint,
std::string* signature,
ErrorPtr* error) {
auto session = pending_sessions_.find(session_id);
if (session == pending_sessions_.end()) {
Error::AddToPrintf(error, FROM_HERE, errors::kUnknownSession,
"Unknown session id: '%s'", session_id.c_str());
return false;
}
std::vector<uint8_t> commitment;
if (!Base64Decode(client_commitment, &commitment)) {
ClosePendingSession(session_id);
Error::AddToPrintf(error, FROM_HERE, errors::kInvalidFormat,
"Invalid commitment string: '%s'",
client_commitment.c_str());
return false;
}
if (!session->second->ProcessMessage(
std::string(commitment.begin(), commitment.end()), error)) {
ClosePendingSession(session_id);
return Error::AddTo(error, FROM_HERE, errors::kCommitmentMismatch,
"Pairing code or crypto implementation mismatch");
}
const std::string& key = session->second->GetKey();
VLOG(3) << "KEY " << base::HexEncode(key.data(), key.size());
const auto& certificate_fingerprint =
auth_manager_->GetCertificateFingerprint();
*fingerprint = Base64Encode(certificate_fingerprint);
std::vector<uint8_t> cert_hmac = HmacSha256(
std::vector<uint8_t>(key.begin(), key.end()), certificate_fingerprint);
*signature = Base64Encode(cert_hmac);
confirmed_sessions_.insert(
std::make_pair(session->first, std::move(session->second)));
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
weak_ptr_factory_.GetWeakPtr(), session_id),
base::TimeDelta::FromMinutes(kSessionExpirationTimeMinutes));
ClosePendingSession(session_id);
return true;
}
bool SecurityManager::CancelPairing(const std::string& session_id,
ErrorPtr* error) {
bool confirmed = CloseConfirmedSession(session_id);
bool pending = ClosePendingSession(session_id);
if (pending) {
CHECK_GE(pairing_attemts_, 1);
--pairing_attemts_;
}
CHECK(!confirmed || !pending);
if (confirmed || pending)
return true;
Error::AddToPrintf(error, FROM_HERE, errors::kUnknownSession,
"Unknown session id: '%s'", session_id.c_str());
return false;
}
std::string SecurityManager::CreateSessionId() {
return auth_manager_->CreateSessionId();
}
void SecurityManager::RegisterPairingListeners(
const PairingStartListener& on_start,
const PairingEndListener& on_end) {
CHECK(on_start_.is_null() && on_end_.is_null());
on_start_ = on_start;
on_end_ = on_end;
}
bool SecurityManager::CheckIfPairingAllowed(ErrorPtr* error) {
if (block_pairing_until_ > auth_manager_->Now()) {
return Error::AddTo(error, FROM_HERE, errors::kDeviceBusy,
"Too many pairing attempts");
}
if (++pairing_attemts_ >= kMaxAllowedPairingAttemts) {
LOG(INFO) << "Pairing blocked for" << kPairingBlockingTimeMinutes
<< "minutes.";
block_pairing_until_ = auth_manager_->Now();
block_pairing_until_ +=
base::TimeDelta::FromMinutes(kPairingBlockingTimeMinutes);
}
return true;
}
bool SecurityManager::ClosePendingSession(const std::string& session_id) {
// The most common source of these session_id values is the map containing
// the sessions, which we're about to clear out. Make a local copy.
const std::string safe_session_id{session_id};
const size_t num_erased = pending_sessions_.erase(safe_session_id);
if (num_erased > 0 && !on_end_.is_null())
on_end_.Run(safe_session_id);
return num_erased != 0;
}
bool SecurityManager::CloseConfirmedSession(const std::string& session_id) {
return confirmed_sessions_.erase(session_id) != 0;
}
bool SecurityManager::IsAnonymousAuthSupported() const {
return GetSettings().local_anonymous_access_role != AuthScope::kNone;
}
bool SecurityManager::IsPairingAuthSupported() const {
return GetSettings().local_pairing_enabled;
}
bool SecurityManager::IsLocalAuthSupported() const {
return GetSettings().root_client_token_owner != RootClientTokenOwner::kNone;
}
} // namespace privet
} // namespace weave