| // 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: | 
 |         Error::AddTo(error, FROM_HERE, errors::kDomain, | 
 |                      errors::kInvalidClientCommitment, spake_.error()); | 
 |         return false; | 
 |       default: | 
 |         LOG(FATAL) << "SecurityManager uses only one round trip"; | 
 |     } | 
 |     return false; | 
 |   } | 
 |  | 
 |   const std::string& GetKey() const override { | 
 |     return spake_.GetUnverifiedKey(); | 
 |   } | 
 |  | 
 |  private: | 
 |   crypto::P224EncryptedKeyExchange spake_; | 
 | }; | 
 |  | 
 | class UnsecureKeyExchanger : public SecurityManager::KeyExchanger { | 
 |  public: | 
 |   explicit UnsecureKeyExchanger(const std::string& password) | 
 |       : password_(password) {} | 
 |   ~UnsecureKeyExchanger() override = default; | 
 |  | 
 |   // SecurityManager::KeyExchanger methods. | 
 |   const std::string& GetMessage() override { return password_; } | 
 |  | 
 |   bool ProcessMessage(const std::string& message, ErrorPtr* error) override { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const std::string& GetKey() const override { return password_; } | 
 |  | 
 |  private: | 
 |   std::string password_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | SecurityManager::SecurityManager(const Config* config, | 
 |                                  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) { | 
 |   UserInfo user_info{desired_scope, | 
 |                      std::to_string(static_cast<int>(auth_type)) + "/" + | 
 |                          std::to_string(++last_user_id_)}; | 
 |  | 
 |   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) { | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidAuthMode, | 
 |                  "Mode is not available"); | 
 |     return false; | 
 |   }; | 
 |  | 
 |   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)) { | 
 |         Error::AddTo(error, FROM_HERE, errors::kDomain, | 
 |                      errors::kInvalidAuthCode, "Invalid authCode"); | 
 |         return false; | 
 |       } | 
 |       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); | 
 |       return auth_manager_->CreateAccessTokenFromAuth( | 
 |           auth_code, kTtl, access_token, access_token_scope, access_token_ttl, | 
 |           error); | 
 |   } | 
 |  | 
 |   Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidAuthMode, | 
 |                "Unsupported auth mode"); | 
 |   return false; | 
 | } | 
 |  | 
 | 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::kDomain, | 
 |                        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::kDomain, | 
 |                        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}; | 
 |   if (GetSettings().disable_security) | 
 |     result.insert(CryptoType::kNone); | 
 |   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::kDomain, | 
 |                        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 { | 
 |   if (GetSettings().disable_security) | 
 |     return true; | 
 |   for (const auto& session : confirmed_sessions_) { | 
 |     const std::string& key = session.second->GetKey(); | 
 |     const std::string& id = session.first; | 
 |     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 invalide 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()) { | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams, | 
 |                  "Pairing mode is not enabled"); | 
 |     return false; | 
 |   } | 
 |  | 
 |   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: | 
 |       Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams, | 
 |                    "Unsupported pairing mode"); | 
 |       return false; | 
 |   } | 
 |  | 
 |   std::unique_ptr<KeyExchanger> spake; | 
 |   switch (crypto) { | 
 |     case CryptoType::kSpake_p224: | 
 |       spake.reset(new Spakep224Exchanger(code)); | 
 |       break; | 
 |     case CryptoType::kNone: | 
 |       if (GetSettings().disable_security) { | 
 |         spake.reset(new UnsecureKeyExchanger(code)); | 
 |         break; | 
 |       } | 
 |     // Fall through... | 
 |     default: | 
 |       Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams, | 
 |                    "Unsupported crypto"); | 
 |       return false; | 
 |   } | 
 |  | 
 |   // 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::kDomain, | 
 |                        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::kDomain, 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); | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kCommitmentMismatch, | 
 |                  "Pairing code or crypto implementation mismatch"); | 
 |     return false; | 
 |   } | 
 |  | 
 |   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::kDomain, errors::kUnknownSession, | 
 |                      "Unknown session id: '%s'", session_id.c_str()); | 
 |   return false; | 
 | } | 
 |  | 
 | std::string SecurityManager::CreateSessionId() { | 
 |   return Base64Encode(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 (GetSettings().disable_security) | 
 |     return true; | 
 |  | 
 |   if (block_pairing_until_ > auth_manager_->Now()) { | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kDeviceBusy, | 
 |                  "Too many pairing attempts"); | 
 |     return false; | 
 |   } | 
 |  | 
 |   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 |