blob: 6236d786c1aaa6dd3df8b240157492a7ab53d9dc [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 <cctype>
9#include <functional>
10#include <memory>
11#include <string>
12#include <utility>
13#include <vector>
14
15#include <base/bind.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070016#include <base/logging.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070017#include <base/rand_util.h>
18#include <base/strings/string_number_conversions.h>
19#include <base/strings/string_util.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070020#include <gmock/gmock.h>
21#include <gtest/gtest.h>
Vitaly Buka727f3e62015-09-25 17:33:43 -070022#include <weave/provider/test/fake_task_runner.h>
Vitaly Buka7ce499f2015-06-09 08:04:11 -070023
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020024#include "src/data_encoding.h"
Vitaly Bukaf08caeb2015-12-02 13:47:48 -080025#include "src/privet/auth_manager.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020026#include "src/privet/openssl_utils.h"
Vitaly Buka9e5b6832015-10-14 15:57:14 -070027#include "third_party/chromium/crypto/p224_spake.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -070028
29using testing::Eq;
30using testing::_;
31
Vitaly Bukab6f015a2015-07-09 14:59:23 -070032namespace weave {
33namespace privet {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070034
35namespace {
36
37bool IsBase64Char(char c) {
38 return isalnum(c) || (c == '+') || (c == '/') || (c == '=');
39}
40
41bool IsBase64(const std::string& text) {
42 return !text.empty() &&
43 !std::any_of(text.begin(), text.end(),
44 std::not1(std::ref(IsBase64Char)));
45}
46
47class MockPairingCallbacks {
48 public:
49 MOCK_METHOD3(OnPairingStart,
50 void(const std::string& session_id,
51 PairingType pairing_type,
52 const std::vector<uint8_t>& code));
53 MOCK_METHOD1(OnPairingEnd, void(const std::string& session_id));
54};
55
Vitaly Buka7ce499f2015-06-09 08:04:11 -070056} // namespace
57
58class SecurityManagerTest : public testing::Test {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070059 protected:
60 void PairAndAuthenticate(std::string* fingerprint, std::string* signature) {
61 std::string session_id;
62 std::string device_commitment_base64;
63
64 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
65 CryptoType::kSpake_p224, &session_id,
66 &device_commitment_base64, nullptr));
67 EXPECT_FALSE(session_id.empty());
68 EXPECT_FALSE(device_commitment_base64.empty());
69
70 crypto::P224EncryptedKeyExchange spake{
71 crypto::P224EncryptedKeyExchange::kPeerTypeClient, "1234"};
72
Vitaly Buka7d556392015-08-13 20:06:48 -070073 std::string client_commitment_base64{Base64Encode(spake.GetNextMessage())};
Vitaly Buka7ce499f2015-06-09 08:04:11 -070074
75 EXPECT_TRUE(security_.ConfirmPairing(session_id, client_commitment_base64,
76 fingerprint, signature, nullptr));
77 EXPECT_TRUE(IsBase64(*fingerprint));
78 EXPECT_TRUE(IsBase64(*signature));
79
Vitaly Bukaa04405e2015-08-13 18:28:14 -070080 std::vector<uint8_t> device_commitment;
Vitaly Buka7d556392015-08-13 20:06:48 -070081 ASSERT_TRUE(Base64Decode(device_commitment_base64, &device_commitment));
Vitaly Buka7ce499f2015-06-09 08:04:11 -070082 spake.ProcessMessage(
Vitaly Buka24d6fd52015-08-13 23:22:48 -070083 std::string(device_commitment.begin(), device_commitment.end()));
Vitaly Buka7ce499f2015-06-09 08:04:11 -070084
Vitaly Bukaa04405e2015-08-13 18:28:14 -070085 const std::string& key = spake.GetUnverifiedKey();
86 std::vector<uint8_t> auth_code{
87 HmacSha256(std::vector<uint8_t>{key.begin(), key.end()},
88 std::vector<uint8_t>{session_id.begin(), session_id.end()})};
Vitaly Buka7ce499f2015-06-09 08:04:11 -070089
Vitaly Buka7d556392015-08-13 20:06:48 -070090 std::string auth_code_base64{Base64Encode(auth_code)};
Vitaly Buka7ce499f2015-06-09 08:04:11 -070091
92 EXPECT_TRUE(security_.IsValidPairingCode(auth_code_base64));
93 }
94
95 const base::Time time_ = base::Time::FromTimeT(1410000000);
Vitaly Buka727f3e62015-09-25 17:33:43 -070096 provider::test::FakeTaskRunner task_runner_;
Vitaly Bukaf08caeb2015-12-02 13:47:48 -080097 AuthManager auth_manager_{
98 {},
99 {{
100 59, 47, 77, 247, 129, 187, 188, 158, 172, 105, 246, 93, 102, 83, 8,
101 138, 176, 141, 37, 63, 223, 40, 153, 121, 134, 23, 120, 106, 24, 205,
102 7, 135,
103 }}};
104 SecurityManager security_{&auth_manager_,
Vitaly Buka8589b052015-09-29 00:46:14 -0700105 {PairingType::kEmbeddedCode},
Vitaly Buka8cb91d72015-08-16 00:40:51 -0700106 "1234",
107 false,
108 &task_runner_};
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700109};
110
111TEST_F(SecurityManagerTest, IsBase64) {
112 EXPECT_TRUE(IsBase64(
113 security_.CreateAccessToken(UserInfo{AuthScope::kUser, 7}, time_)));
114}
115
116TEST_F(SecurityManagerTest, CreateSameToken) {
117 EXPECT_EQ(
118 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_),
119 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_));
120}
121
122TEST_F(SecurityManagerTest, CreateTokenDifferentScope) {
123 EXPECT_NE(
124 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}, time_),
125 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_));
126}
127
128TEST_F(SecurityManagerTest, CreateTokenDifferentUser) {
129 EXPECT_NE(
130 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_),
131 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}, time_));
132}
133
134TEST_F(SecurityManagerTest, CreateTokenDifferentTime) {
135 EXPECT_NE(
136 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}, time_),
137 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567},
138 base::Time::FromTimeT(1400000000)));
139}
140
141TEST_F(SecurityManagerTest, CreateTokenDifferentInstance) {
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800142 AuthManager auth{{}, {}};
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700143 EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_),
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800144 SecurityManager(&auth, {}, "", false, &task_runner_)
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700145 .CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_));
146}
147
148TEST_F(SecurityManagerTest, ParseAccessToken) {
149 // Multiple attempts with random secrets.
150 for (size_t i = 0; i < 1000; ++i) {
Vitaly Bukaf08caeb2015-12-02 13:47:48 -0800151 AuthManager auth{{}, {}};
152 SecurityManager security{&auth, {}, "", false, &task_runner_};
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700153
154 std::string token =
155 security.CreateAccessToken(UserInfo{AuthScope::kUser, 5}, time_);
156 base::Time time2;
157 EXPECT_EQ(AuthScope::kUser,
158 security.ParseAccessToken(token, &time2).scope());
159 EXPECT_EQ(5u, security.ParseAccessToken(token, &time2).user_id());
160 // Token timestamp resolution is one second.
161 EXPECT_GE(1, std::abs((time_ - time2).InSeconds()));
162 }
163}
164
165TEST_F(SecurityManagerTest, PairingNoSession) {
166 std::string fingerprint;
167 std::string signature;
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700168 ErrorPtr error;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700169 ASSERT_FALSE(
170 security_.ConfirmPairing("123", "345", &fingerprint, &signature, &error));
171 EXPECT_EQ("unknownSession", error->GetCode());
172}
173
174TEST_F(SecurityManagerTest, Pairing) {
175 std::vector<std::pair<std::string, std::string> > fingerprints(2);
176 for (auto& it : fingerprints) {
177 PairAndAuthenticate(&it.first, &it.second);
178 }
179
180 // Same certificate.
181 EXPECT_EQ(fingerprints.front().first, fingerprints.back().first);
182
183 // Signed with different secret.
184 EXPECT_NE(fingerprints.front().second, fingerprints.back().second);
185}
186
187TEST_F(SecurityManagerTest, NotifiesListenersOfSessionStartAndEnd) {
188 testing::StrictMock<MockPairingCallbacks> callbacks;
189 security_.RegisterPairingListeners(
190 base::Bind(&MockPairingCallbacks::OnPairingStart,
191 base::Unretained(&callbacks)),
192 base::Bind(&MockPairingCallbacks::OnPairingEnd,
193 base::Unretained(&callbacks)));
194 for (auto commitment_suffix :
195 std::vector<std::string>{"", "invalid_commitment"}) {
196 // StartPairing should notify us that a new session has begun.
197 std::string session_id;
198 std::string device_commitment;
199 EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
200 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
201 CryptoType::kSpake_p224, &session_id,
202 &device_commitment, nullptr));
203 EXPECT_FALSE(session_id.empty());
204 EXPECT_FALSE(device_commitment.empty());
205 testing::Mock::VerifyAndClearExpectations(&callbacks);
206
207 // ConfirmPairing should notify us that the session has ended.
208 EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
209 crypto::P224EncryptedKeyExchange spake{
210 crypto::P224EncryptedKeyExchange::kPeerTypeServer, "1234"};
Vitaly Buka7d556392015-08-13 20:06:48 -0700211 std::string client_commitment = Base64Encode(spake.GetNextMessage());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700212 std::string fingerprint, signature;
213 // Regardless of whether the commitment is valid or not, we should get a
214 // callback indicating that the pairing session is gone.
215 security_.ConfirmPairing(session_id, client_commitment + commitment_suffix,
216 &fingerprint, &signature, nullptr);
217 testing::Mock::VerifyAndClearExpectations(&callbacks);
218 }
219}
220
221TEST_F(SecurityManagerTest, CancelPairing) {
222 testing::StrictMock<MockPairingCallbacks> callbacks;
223 security_.RegisterPairingListeners(
224 base::Bind(&MockPairingCallbacks::OnPairingStart,
225 base::Unretained(&callbacks)),
226 base::Bind(&MockPairingCallbacks::OnPairingEnd,
227 base::Unretained(&callbacks)));
228 std::string session_id;
229 std::string device_commitment;
230 EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
231 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
232 CryptoType::kSpake_p224, &session_id,
233 &device_commitment, nullptr));
234 EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
235 EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
236}
237
238TEST_F(SecurityManagerTest, ThrottlePairing) {
239 auto pair = [this]() {
240 std::string session_id;
241 std::string device_commitment;
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700242 ErrorPtr error;
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700243 bool result = security_.StartPairing(PairingType::kEmbeddedCode,
244 CryptoType::kSpake_p224, &session_id,
245 &device_commitment, &error);
246 EXPECT_TRUE(result || error->GetCode() == "deviceBusy");
247 return result;
248 };
249
250 EXPECT_TRUE(pair());
251 EXPECT_TRUE(pair());
252 EXPECT_TRUE(pair());
253 EXPECT_FALSE(pair());
254 EXPECT_GT(security_.block_pairing_until_, base::Time::Now());
255 EXPECT_LE(security_.block_pairing_until_,
256 base::Time::Now() + base::TimeDelta::FromMinutes(15));
257
258 // Wait timeout.
259 security_.block_pairing_until_ =
260 base::Time::Now() - base::TimeDelta::FromMinutes(1);
261
262 // Allow exactly one attempt.
263 EXPECT_TRUE(pair());
264 EXPECT_FALSE(pair());
265
266 // Wait timeout.
267 security_.block_pairing_until_ =
268 base::Time::Now() - base::TimeDelta::FromMinutes(1);
269
270 // Completely unblock by successfully pairing.
271 std::string fingerprint;
272 std::string signature;
273 PairAndAuthenticate(&fingerprint, &signature);
274
275 // Now we have 3 attempts again.
276 EXPECT_TRUE(pair());
277 EXPECT_TRUE(pair());
278 EXPECT_TRUE(pair());
279 EXPECT_FALSE(pair());
280}
281
282TEST_F(SecurityManagerTest, DontBlockForCanceledSessions) {
283 for (int i = 0; i < 20; ++i) {
284 std::string session_id;
285 std::string device_commitment;
286 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
287 CryptoType::kSpake_p224, &session_id,
288 &device_commitment, nullptr));
289 EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
290 }
291}
292
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700293} // namespace privet
294} // namespace weave