blob: a838daeba1d4a015ef8548ba6cb72a64dc2ffc4e [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"
Vitaly Bukaf08caeb2015-12-02 13:47:48 -080022#include "src/privet/auth_manager.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020023#include "src/privet/constants.h"
24#include "src/privet/openssl_utils.h"
25#include "src/string_utils.h"
Vitaly Buka9e5b6832015-10-14 15:57:14 -070026#include "third_party/chromium/crypto/p224_spake.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -070027
Vitaly Bukab6f015a2015-07-09 14:59:23 -070028namespace weave {
29namespace privet {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070030
31namespace {
32
33const char kTokenDelimeter[] = ":";
34const int kSessionExpirationTimeMinutes = 5;
35const int kPairingExpirationTimeMinutes = 5;
36const int kMaxAllowedPairingAttemts = 3;
37const int kPairingBlockingTimeMinutes = 1;
38
Vitaly Buka7ce499f2015-06-09 08:04:11 -070039// Returns "scope:id:time".
40std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
41 return base::IntToString(static_cast<int>(user_info.scope())) +
42 kTokenDelimeter + base::Uint64ToString(user_info.user_id()) +
43 kTokenDelimeter + base::Int64ToString(time.ToTimeT());
44}
45
46// Splits string of "scope:id:time" format.
47UserInfo SplitTokenData(const std::string& token, base::Time* time) {
48 const UserInfo kNone;
Vitaly Buka24d6fd52015-08-13 23:22:48 -070049 auto parts = Split(token, kTokenDelimeter, false, false);
Vitaly Buka7ce499f2015-06-09 08:04:11 -070050 if (parts.size() != 3)
51 return kNone;
52 int scope = 0;
53 if (!base::StringToInt(parts[0], &scope) ||
54 scope < static_cast<int>(AuthScope::kNone) ||
55 scope > static_cast<int>(AuthScope::kOwner)) {
56 return kNone;
57 }
58
59 uint64_t id{0};
60 if (!base::StringToUint64(parts[1], &id))
61 return kNone;
62
63 int64_t timestamp{0};
64 if (!base::StringToInt64(parts[2], &timestamp))
65 return kNone;
66 *time = base::Time::FromTimeT(timestamp);
67 return UserInfo{static_cast<AuthScope>(scope), id};
68}
69
Vitaly Buka7ce499f2015-06-09 08:04:11 -070070class Spakep224Exchanger : public SecurityManager::KeyExchanger {
71 public:
72 explicit Spakep224Exchanger(const std::string& password)
73 : spake_(crypto::P224EncryptedKeyExchange::kPeerTypeServer, password) {}
74 ~Spakep224Exchanger() override = default;
75
76 // SecurityManager::KeyExchanger methods.
77 const std::string& GetMessage() override { return spake_.GetNextMessage(); }
78
Vitaly Buka0801a1f2015-08-14 10:03:46 -070079 bool ProcessMessage(const std::string& message, ErrorPtr* error) override {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070080 switch (spake_.ProcessMessage(message)) {
81 case crypto::P224EncryptedKeyExchange::kResultPending:
82 return true;
83 case crypto::P224EncryptedKeyExchange::kResultFailed:
Vitaly Buka0801a1f2015-08-14 10:03:46 -070084 Error::AddTo(error, FROM_HERE, errors::kDomain,
85 errors::kInvalidClientCommitment, spake_.error());
Vitaly Buka7ce499f2015-06-09 08:04:11 -070086 return false;
87 default:
88 LOG(FATAL) << "SecurityManager uses only one round trip";
89 }
90 return false;
91 }
92
93 const std::string& GetKey() const override {
94 return spake_.GetUnverifiedKey();
95 }
96
97 private:
98 crypto::P224EncryptedKeyExchange spake_;
99};
100
101class UnsecureKeyExchanger : public SecurityManager::KeyExchanger {
102 public:
103 explicit UnsecureKeyExchanger(const std::string& password)
104 : password_(password) {}
105 ~UnsecureKeyExchanger() override = default;
106
107 // SecurityManager::KeyExchanger methods.
108 const std::string& GetMessage() override { return password_; }
109
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700110 bool ProcessMessage(const std::string& message, ErrorPtr* error) override {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700111 return true;
112 }
113
114 const std::string& GetKey() const override { return password_; }
115
116 private:
117 std::string password_;
118};
119
120} // namespace
121
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800122SecurityManager::SecurityManager(AuthManager* auth_manager,
Vitaly Buka8589b052015-09-29 00:46:14 -0700123 const std::set<PairingType>& pairing_modes,
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700124 const std::string& embedded_code,
125 bool disable_security,
Vitaly Buka1e363672015-09-25 14:01:16 -0700126 provider::TaskRunner* task_runner)
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800127 : auth_manager_{auth_manager},
128 is_security_disabled_(disable_security),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700129 pairing_modes_(pairing_modes),
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700130 embedded_code_(embedded_code),
Vitaly Buka8589b052015-09-29 00:46:14 -0700131 task_runner_{task_runner} {
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800132 CHECK(auth_manager_);
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700133 CHECK_EQ(embedded_code_.empty(),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700134 std::find(pairing_modes_.begin(), pairing_modes_.end(),
135 PairingType::kEmbeddedCode) == pairing_modes_.end());
136}
137
138SecurityManager::~SecurityManager() {
139 while (!pending_sessions_.empty())
140 ClosePendingSession(pending_sessions_.begin()->first);
141}
142
143// Returns "base64([hmac]scope:id:time)".
144std::string SecurityManager::CreateAccessToken(const UserInfo& user_info,
145 const base::Time& time) {
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800146 return Base64Encode(auth_manager_->CreateAccessToken(user_info, time));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700147}
148
149// Parses "base64([hmac]scope:id:time)".
150UserInfo SecurityManager::ParseAccessToken(const std::string& token,
151 base::Time* time) const {
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700152 std::vector<uint8_t> decoded;
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800153 if (!Base64Decode(token, &decoded))
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700154 return UserInfo{};
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800155
156 return auth_manager_->ParseAccessToken(decoded, time);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700157}
158
159std::set<PairingType> SecurityManager::GetPairingTypes() const {
160 return pairing_modes_;
161}
162
163std::set<CryptoType> SecurityManager::GetCryptoTypes() const {
164 std::set<CryptoType> result{CryptoType::kSpake_p224};
165 if (is_security_disabled_)
166 result.insert(CryptoType::kNone);
167 return result;
168}
169
170bool SecurityManager::IsValidPairingCode(const std::string& auth_code) const {
171 if (is_security_disabled_)
172 return true;
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700173 std::vector<uint8_t> auth_decoded;
Vitaly Buka7d556392015-08-13 20:06:48 -0700174 if (!Base64Decode(auth_code, &auth_decoded))
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700175 return false;
176 for (const auto& session : confirmed_sessions_) {
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700177 const std::string& key = session.second->GetKey();
178 const std::string& id = session.first;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700179 if (auth_decoded ==
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700180 HmacSha256(std::vector<uint8_t>(key.begin(), key.end()),
181 std::vector<uint8_t>(id.begin(), id.end()))) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700182 pairing_attemts_ = 0;
183 block_pairing_until_ = base::Time{};
184 return true;
185 }
186 }
187 LOG(ERROR) << "Attempt to authenticate with invalide code.";
188 return false;
189}
190
191bool SecurityManager::StartPairing(PairingType mode,
192 CryptoType crypto,
193 std::string* session_id,
194 std::string* device_commitment,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700195 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700196 if (!CheckIfPairingAllowed(error))
197 return false;
198
199 if (std::find(pairing_modes_.begin(), pairing_modes_.end(), mode) ==
200 pairing_modes_.end()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700201 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
202 "Pairing mode is not enabled");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700203 return false;
204 }
205
206 std::string code;
207 switch (mode) {
208 case PairingType::kEmbeddedCode:
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700209 CHECK(!embedded_code_.empty());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700210 code = embedded_code_;
211 break;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700212 case PairingType::kPinCode:
213 code = base::StringPrintf("%04i", base::RandInt(0, 9999));
214 break;
215 default:
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700216 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
217 "Unsupported pairing mode");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700218 return false;
219 }
220
221 std::unique_ptr<KeyExchanger> spake;
222 switch (crypto) {
223 case CryptoType::kSpake_p224:
224 spake.reset(new Spakep224Exchanger(code));
225 break;
226 case CryptoType::kNone:
227 if (is_security_disabled_) {
228 spake.reset(new UnsecureKeyExchanger(code));
229 break;
230 }
231 // Fall through...
232 default:
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700233 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
234 "Unsupported crypto");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700235 return false;
236 }
237
238 // Allow only a single session at a time for now.
239 while (!pending_sessions_.empty())
240 ClosePendingSession(pending_sessions_.begin()->first);
241
242 std::string session;
243 do {
244 session = base::GenerateGUID();
245 } while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
246 pending_sessions_.find(session) != pending_sessions_.end());
247 std::string commitment = spake->GetMessage();
Vitaly Buka52d006a2015-11-21 17:14:51 -0800248 pending_sessions_.insert(std::make_pair(session, std::move(spake)));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700249
Vitaly Bukaf9630fb2015-08-12 21:15:40 -0700250 task_runner_->PostDelayedTask(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700251 FROM_HERE,
252 base::Bind(base::IgnoreResult(&SecurityManager::ClosePendingSession),
253 weak_ptr_factory_.GetWeakPtr(), session),
254 base::TimeDelta::FromMinutes(kPairingExpirationTimeMinutes));
255
256 *session_id = session;
Vitaly Buka7d556392015-08-13 20:06:48 -0700257 *device_commitment = Base64Encode(commitment);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700258 LOG(INFO) << "Pairing code for session " << *session_id << " is " << code;
259 // TODO(vitalybuka): Handle case when device can't start multiple pairing
260 // simultaneously and implement throttling to avoid brute force attack.
261 if (!on_start_.is_null()) {
262 on_start_.Run(session, mode,
Vitaly Buka24d6fd52015-08-13 23:22:48 -0700263 std::vector<uint8_t>{code.begin(), code.end()});
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700264 }
265
266 return true;
267}
268
269bool SecurityManager::ConfirmPairing(const std::string& session_id,
270 const std::string& client_commitment,
271 std::string* fingerprint,
272 std::string* signature,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700273 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700274 auto session = pending_sessions_.find(session_id);
275 if (session == pending_sessions_.end()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700276 Error::AddToPrintf(error, FROM_HERE, errors::kDomain,
277 errors::kUnknownSession, "Unknown session id: '%s'",
278 session_id.c_str());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700279 return false;
280 }
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700281
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700282 std::vector<uint8_t> commitment;
Vitaly Buka7d556392015-08-13 20:06:48 -0700283 if (!Base64Decode(client_commitment, &commitment)) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700284 ClosePendingSession(session_id);
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700285 Error::AddToPrintf(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700286 error, FROM_HERE, errors::kDomain, errors::kInvalidFormat,
287 "Invalid commitment string: '%s'", client_commitment.c_str());
288 return false;
289 }
290
291 if (!session->second->ProcessMessage(
292 std::string(commitment.begin(), commitment.end()), error)) {
293 ClosePendingSession(session_id);
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700294 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kCommitmentMismatch,
295 "Pairing code or crypto implementation mismatch");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700296 return false;
297 }
298
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700299 const std::string& key = session->second->GetKey();
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700300 VLOG(3) << "KEY " << base::HexEncode(key.data(), key.size());
301
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800302 const auto& certificate_fingerprint =
303 auth_manager_->GetCertificateFingerprint();
304 *fingerprint = Base64Encode(certificate_fingerprint);
Vitaly Bukaa04405e2015-08-13 18:28:14 -0700305 std::vector<uint8_t> cert_hmac = HmacSha256(
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800306 std::vector<uint8_t>(key.begin(), key.end()), certificate_fingerprint);
Vitaly Buka7d556392015-08-13 20:06:48 -0700307 *signature = Base64Encode(cert_hmac);
Vitaly Buka52d006a2015-11-21 17:14:51 -0800308 confirmed_sessions_.insert(
309 std::make_pair(session->first, std::move(session->second)));
Vitaly Bukaf9630fb2015-08-12 21:15:40 -0700310 task_runner_->PostDelayedTask(
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700311 FROM_HERE,
312 base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
313 weak_ptr_factory_.GetWeakPtr(), session_id),
314 base::TimeDelta::FromMinutes(kSessionExpirationTimeMinutes));
315 ClosePendingSession(session_id);
316 return true;
317}
318
319bool SecurityManager::CancelPairing(const std::string& session_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700320 ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700321 bool confirmed = CloseConfirmedSession(session_id);
322 bool pending = ClosePendingSession(session_id);
323 if (pending) {
324 CHECK_GE(pairing_attemts_, 1);
325 --pairing_attemts_;
326 }
327 CHECK(!confirmed || !pending);
328 if (confirmed || pending)
329 return true;
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700330 Error::AddToPrintf(error, FROM_HERE, errors::kDomain, errors::kUnknownSession,
331 "Unknown session id: '%s'", session_id.c_str());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700332 return false;
333}
334
335void SecurityManager::RegisterPairingListeners(
336 const PairingStartListener& on_start,
337 const PairingEndListener& on_end) {
338 CHECK(on_start_.is_null() && on_end_.is_null());
339 on_start_ = on_start;
Vitaly Buka075b3d42015-06-09 08:34:25 -0700340 on_end_ = on_end;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700341}
342
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700343bool SecurityManager::CheckIfPairingAllowed(ErrorPtr* error) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700344 if (is_security_disabled_)
345 return true;
346
347 if (block_pairing_until_ > base::Time::Now()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700348 Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kDeviceBusy,
349 "Too many pairing attempts");
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700350 return false;
351 }
352
353 if (++pairing_attemts_ >= kMaxAllowedPairingAttemts) {
354 LOG(INFO) << "Pairing blocked for" << kPairingBlockingTimeMinutes
355 << "minutes.";
356 block_pairing_until_ = base::Time::Now();
357 block_pairing_until_ +=
358 base::TimeDelta::FromMinutes(kPairingBlockingTimeMinutes);
359 }
360
361 return true;
362}
363
364bool SecurityManager::ClosePendingSession(const std::string& session_id) {
365 // The most common source of these session_id values is the map containing
366 // the sessions, which we're about to clear out. Make a local copy.
367 const std::string safe_session_id{session_id};
368 const size_t num_erased = pending_sessions_.erase(safe_session_id);
369 if (num_erased > 0 && !on_end_.is_null())
370 on_end_.Run(safe_session_id);
371 return num_erased != 0;
372}
373
374bool SecurityManager::CloseConfirmedSession(const std::string& session_id) {
375 return confirmed_sessions_.erase(session_id) != 0;
376}
377
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700378} // namespace privet
379} // namespace weave