blob: 7e35e79d1af33589de833ededc9b43376992be3b [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Vitaly Buka7ce499f2015-06-09 08:04:11 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stefan Sauer2d16dfa2015-09-25 17:08:35 +02005#include "src/privet/security_manager.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -07006
7#include <algorithm>
8#include <limits>
9#include <memory>
10#include <set>
11
12#include <base/bind.h>
13#include <base/guid.h>
14#include <base/logging.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070015#include <base/rand_util.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070016#include <base/strings/string_number_conversions.h>
17#include <base/strings/stringprintf.h>
18#include <base/time/time.h>
Vitaly Buka1e363672015-09-25 14:01:16 -070019#include <weave/provider/task_runner.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070020
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020021#include "src/data_encoding.h"
22#include "src/privet/constants.h"
23#include "src/privet/openssl_utils.h"
24#include "src/string_utils.h"
Vitaly Buka9e5b6832015-10-14 15:57:14 -070025#include "third_party/chromium/crypto/p224_spake.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -070026
Vitaly Bukab6f015a2015-07-09 14:59:23 -070027namespace weave {
28namespace privet {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070029
30namespace {
31
32const char kTokenDelimeter[] = ":";
33const int kSessionExpirationTimeMinutes = 5;
34const int kPairingExpirationTimeMinutes = 5;
35const int kMaxAllowedPairingAttemts = 3;
36const int kPairingBlockingTimeMinutes = 1;
37
Vitaly Buka7ce499f2015-06-09 08:04:11 -070038// Returns "scope:id:time".
39std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
40 return base::IntToString(static_cast<int>(user_info.scope())) +
41 kTokenDelimeter + base::Uint64ToString(user_info.user_id()) +
42 kTokenDelimeter + base::Int64ToString(time.ToTimeT());
43}
44
45// Splits string of "scope:id:time" format.
46UserInfo SplitTokenData(const std::string& token, base::Time* time) {
47 const UserInfo kNone;
Vitaly Buka24d6fd52015-08-13 23:22:48 -070048 auto parts = Split(token, kTokenDelimeter, false, false);
Vitaly Buka7ce499f2015-06-09 08:04:11 -070049 if (parts.size() != 3)
50 return kNone;
51 int scope = 0;
52 if (!base::StringToInt(parts[0], &scope) ||
53 scope < static_cast<int>(AuthScope::kNone) ||
54 scope > static_cast<int>(AuthScope::kOwner)) {
55 return kNone;
56 }
57
58 uint64_t id{0};
59 if (!base::StringToUint64(parts[1], &id))
60 return kNone;
61
62 int64_t timestamp{0};
63 if (!base::StringToInt64(parts[2], &timestamp))
64 return kNone;
65 *time = base::Time::FromTimeT(timestamp);
66 return UserInfo{static_cast<AuthScope>(scope), id};
67}
68
Vitaly Buka7ce499f2015-06-09 08:04:11 -070069class Spakep224Exchanger : public SecurityManager::KeyExchanger {
70 public:
71 explicit Spakep224Exchanger(const std::string& password)
72 : spake_(crypto::P224EncryptedKeyExchange::kPeerTypeServer, password) {}
73 ~Spakep224Exchanger() override = default;
74
75 // SecurityManager::KeyExchanger methods.
76 const std::string& GetMessage() override { return spake_.GetNextMessage(); }
77
Vitaly Buka0801a1f2015-08-14 10:03:46 -070078 bool ProcessMessage(const std::string& message, ErrorPtr* error) override {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070079 switch (spake_.ProcessMessage(message)) {
80 case crypto::P224EncryptedKeyExchange::kResultPending:
81 return true;
82 case crypto::P224EncryptedKeyExchange::kResultFailed:
Vitaly Buka0801a1f2015-08-14 10:03:46 -070083 Error::AddTo(error, FROM_HERE, errors::kDomain,
84 errors::kInvalidClientCommitment, spake_.error());
Vitaly Buka7ce499f2015-06-09 08:04:11 -070085 return false;
86 default:
87 LOG(FATAL) << "SecurityManager uses only one round trip";
88 }
89 return false;
90 }
91
92 const std::string& GetKey() const override {
93 return spake_.GetUnverifiedKey();
94 }
95
96 private:
97 crypto::P224EncryptedKeyExchange spake_;
98};
99
100class UnsecureKeyExchanger : public SecurityManager::KeyExchanger {
101 public:
102 explicit UnsecureKeyExchanger(const std::string& password)
103 : password_(password) {}
104 ~UnsecureKeyExchanger() override = default;
105
106 // SecurityManager::KeyExchanger methods.
107 const std::string& GetMessage() override { return password_; }
108
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700109 bool ProcessMessage(const std::string& message, ErrorPtr* error) override {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700110 return true;
111 }
112
113 const std::string& GetKey() const override { return password_; }
114
115 private:
116 std::string password_;
117};
118
119} // namespace
120
Vitaly Buka8589b052015-09-29 00:46:14 -0700121SecurityManager::SecurityManager(const std::string& secret,
122 const std::set<PairingType>& pairing_modes,
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700123 const std::string& embedded_code,
124 bool disable_security,
Vitaly Buka1e363672015-09-25 14:01:16 -0700125 provider::TaskRunner* task_runner)
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700126 : is_security_disabled_(disable_security),
127 pairing_modes_(pairing_modes),
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700128 embedded_code_(embedded_code),
Vitaly Buka8589b052015-09-29 00:46:14 -0700129 task_runner_{task_runner} {
130 if (!Base64Decode(secret, &secret_) || secret_.size() != kSha256OutputSize) {
131 secret_.resize(kSha256OutputSize);
132 base::RandBytes(secret_.data(), secret_.size());
133 }
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700134 CHECK_EQ(embedded_code_.empty(),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700135 std::find(pairing_modes_.begin(), pairing_modes_.end(),
136 PairingType::kEmbeddedCode) == pairing_modes_.end());
137}
138
139SecurityManager::~SecurityManager() {
140 while (!pending_sessions_.empty())
141 ClosePendingSession(pending_sessions_.begin()->first);
142}
143
144// Returns "base64([hmac]scope:id:time)".
145std::string SecurityManager::CreateAccessToken(const UserInfo& user_info,
146 const base::Time& time) {
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700147 std::string data_str{CreateTokenData(user_info, time)};
148 std::vector<uint8_t> data{data_str.begin(), data_str.end()};
149 std::vector<uint8_t> hash{HmacSha256(secret_, data)};
150 hash.insert(hash.end(), data.begin(), data.end());
151 return Base64Encode(hash);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700152}
153
154// Parses "base64([hmac]scope:id:time)".
155UserInfo SecurityManager::ParseAccessToken(const std::string& token,
156 base::Time* time) const {
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700157 std::vector<uint8_t> decoded;
Vitaly Buka7d556392015-08-13 20:06:48 -0700158 if (!Base64Decode(token, &decoded) || decoded.size() <= kSha256OutputSize) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700159 return UserInfo{};
160 }
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700161 std::vector<uint8_t> data(decoded.begin() + kSha256OutputSize, decoded.end());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700162 decoded.resize(kSha256OutputSize);
163 if (decoded != HmacSha256(secret_, data))
164 return UserInfo{};
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700165 return SplitTokenData(std::string(data.begin(), data.end()), time);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700166}
167
168std::set<PairingType> SecurityManager::GetPairingTypes() const {
169 return pairing_modes_;
170}
171
172std::set<CryptoType> SecurityManager::GetCryptoTypes() const {
173 std::set<CryptoType> result{CryptoType::kSpake_p224};
174 if (is_security_disabled_)
175 result.insert(CryptoType::kNone);
176 return result;
177}
178
179bool SecurityManager::IsValidPairingCode(const std::string& auth_code) const {
180 if (is_security_disabled_)
181 return true;
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700182 std::vector<uint8_t> auth_decoded;
Vitaly Buka7d556392015-08-13 20:06:48 -0700183 if (!Base64Decode(auth_code, &auth_decoded))
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700184 return false;
185 for (const auto& session : confirmed_sessions_) {
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700186 const std::string& key = session.second->GetKey();
187 const std::string& id = session.first;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700188 if (auth_decoded ==
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700189 HmacSha256(std::vector<uint8_t>(key.begin(), key.end()),
190 std::vector<uint8_t>(id.begin(), id.end()))) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700191 pairing_attemts_ = 0;
192 block_pairing_until_ = base::Time{};
193 return true;
194 }
195 }
196 LOG(ERROR) << "Attempt to authenticate with invalide code.";
197 return false;
198}
199
200bool SecurityManager::StartPairing(PairingType mode,
201 CryptoType crypto,
202 std::string* session_id,
203 std::string* device_commitment,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700204 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700205 if (!CheckIfPairingAllowed(error))
206 return false;
207
208 if (std::find(pairing_modes_.begin(), pairing_modes_.end(), mode) ==
209 pairing_modes_.end()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700210 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
211 "Pairing mode is not enabled");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700212 return false;
213 }
214
215 std::string code;
216 switch (mode) {
217 case PairingType::kEmbeddedCode:
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700218 CHECK(!embedded_code_.empty());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700219 code = embedded_code_;
220 break;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700221 case PairingType::kPinCode:
222 code = base::StringPrintf("%04i", base::RandInt(0, 9999));
223 break;
224 default:
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700225 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
226 "Unsupported pairing mode");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700227 return false;
228 }
229
230 std::unique_ptr<KeyExchanger> spake;
231 switch (crypto) {
232 case CryptoType::kSpake_p224:
233 spake.reset(new Spakep224Exchanger(code));
234 break;
235 case CryptoType::kNone:
236 if (is_security_disabled_) {
237 spake.reset(new UnsecureKeyExchanger(code));
238 break;
239 }
240 // Fall through...
241 default:
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700242 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
243 "Unsupported crypto");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700244 return false;
245 }
246
247 // Allow only a single session at a time for now.
248 while (!pending_sessions_.empty())
249 ClosePendingSession(pending_sessions_.begin()->first);
250
251 std::string session;
252 do {
253 session = base::GenerateGUID();
254 } while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
255 pending_sessions_.find(session) != pending_sessions_.end());
256 std::string commitment = spake->GetMessage();
Vitaly Buka52d006a2015-11-21 17:14:51 -0800257 pending_sessions_.insert(std::make_pair(session, std::move(spake)));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700258
Vitaly Bukaf9630fb2015-08-12 21:15:40 -0700259 task_runner_->PostDelayedTask(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700260 FROM_HERE,
261 base::Bind(base::IgnoreResult(&SecurityManager::ClosePendingSession),
262 weak_ptr_factory_.GetWeakPtr(), session),
263 base::TimeDelta::FromMinutes(kPairingExpirationTimeMinutes));
264
265 *session_id = session;
Vitaly Buka7d556392015-08-13 20:06:48 -0700266 *device_commitment = Base64Encode(commitment);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700267 LOG(INFO) << "Pairing code for session " << *session_id << " is " << code;
268 // TODO(vitalybuka): Handle case when device can't start multiple pairing
269 // simultaneously and implement throttling to avoid brute force attack.
270 if (!on_start_.is_null()) {
271 on_start_.Run(session, mode,
Vitaly Buka24d6fd52015-08-13 23:22:48 -0700272 std::vector<uint8_t>{code.begin(), code.end()});
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700273 }
274
275 return true;
276}
277
278bool SecurityManager::ConfirmPairing(const std::string& session_id,
279 const std::string& client_commitment,
280 std::string* fingerprint,
281 std::string* signature,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700282 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700283 auto session = pending_sessions_.find(session_id);
284 if (session == pending_sessions_.end()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700285 Error::AddToPrintf(error, FROM_HERE, errors::kDomain,
286 errors::kUnknownSession, "Unknown session id: '%s'",
287 session_id.c_str());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700288 return false;
289 }
290 CHECK(!certificate_fingerprint_.empty());
291
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700292 std::vector<uint8_t> commitment;
Vitaly Buka7d556392015-08-13 20:06:48 -0700293 if (!Base64Decode(client_commitment, &commitment)) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700294 ClosePendingSession(session_id);
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700295 Error::AddToPrintf(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700296 error, FROM_HERE, errors::kDomain, errors::kInvalidFormat,
297 "Invalid commitment string: '%s'", client_commitment.c_str());
298 return false;
299 }
300
301 if (!session->second->ProcessMessage(
302 std::string(commitment.begin(), commitment.end()), error)) {
303 ClosePendingSession(session_id);
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700304 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kCommitmentMismatch,
305 "Pairing code or crypto implementation mismatch");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700306 return false;
307 }
308
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700309 const std::string& key = session->second->GetKey();
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700310 VLOG(3) << "KEY " << base::HexEncode(key.data(), key.size());
311
Vitaly Buka7d556392015-08-13 20:06:48 -0700312 *fingerprint = Base64Encode(certificate_fingerprint_);
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700313 std::vector<uint8_t> cert_hmac = HmacSha256(
314 std::vector<uint8_t>(key.begin(), key.end()), certificate_fingerprint_);
Vitaly Buka7d556392015-08-13 20:06:48 -0700315 *signature = Base64Encode(cert_hmac);
Vitaly Buka52d006a2015-11-21 17:14:51 -0800316 confirmed_sessions_.insert(
317 std::make_pair(session->first, std::move(session->second)));
Vitaly Bukaf9630fb2015-08-12 21:15:40 -0700318 task_runner_->PostDelayedTask(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700319 FROM_HERE,
320 base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
321 weak_ptr_factory_.GetWeakPtr(), session_id),
322 base::TimeDelta::FromMinutes(kSessionExpirationTimeMinutes));
323 ClosePendingSession(session_id);
324 return true;
325}
326
327bool SecurityManager::CancelPairing(const std::string& session_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700328 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700329 bool confirmed = CloseConfirmedSession(session_id);
330 bool pending = ClosePendingSession(session_id);
331 if (pending) {
332 CHECK_GE(pairing_attemts_, 1);
333 --pairing_attemts_;
334 }
335 CHECK(!confirmed || !pending);
336 if (confirmed || pending)
337 return true;
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700338 Error::AddToPrintf(error, FROM_HERE, errors::kDomain, errors::kUnknownSession,
339 "Unknown session id: '%s'", session_id.c_str());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700340 return false;
341}
342
343void SecurityManager::RegisterPairingListeners(
344 const PairingStartListener& on_start,
345 const PairingEndListener& on_end) {
346 CHECK(on_start_.is_null() && on_end_.is_null());
347 on_start_ = on_start;
Vitaly Buka075b3d42015-06-09 08:34:25 -0700348 on_end_ = on_end;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700349}
350
Vitaly Buka8589b052015-09-29 00:46:14 -0700351std::string SecurityManager::GetSecret() const {
352 return Base64Encode(secret_);
353}
354
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700355bool SecurityManager::CheckIfPairingAllowed(ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700356 if (is_security_disabled_)
357 return true;
358
359 if (block_pairing_until_ > base::Time::Now()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700360 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kDeviceBusy,
361 "Too many pairing attempts");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700362 return false;
363 }
364
365 if (++pairing_attemts_ >= kMaxAllowedPairingAttemts) {
366 LOG(INFO) << "Pairing blocked for" << kPairingBlockingTimeMinutes
367 << "minutes.";
368 block_pairing_until_ = base::Time::Now();
369 block_pairing_until_ +=
370 base::TimeDelta::FromMinutes(kPairingBlockingTimeMinutes);
371 }
372
373 return true;
374}
375
376bool SecurityManager::ClosePendingSession(const std::string& session_id) {
377 // The most common source of these session_id values is the map containing
378 // the sessions, which we're about to clear out. Make a local copy.
379 const std::string safe_session_id{session_id};
380 const size_t num_erased = pending_sessions_.erase(safe_session_id);
381 if (num_erased > 0 && !on_end_.is_null())
382 on_end_.Run(safe_session_id);
383 return num_erased != 0;
384}
385
386bool SecurityManager::CloseConfirmedSession(const std::string& session_id) {
387 return confirmed_sessions_.erase(session_id) != 0;
388}
389
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700390} // namespace privet
391} // namespace weave