blob: 9c995be736c4f5a22094ab92492c5d078fefc63c [file] [log] [blame]
Vitaly Buka7ce499f2015-06-09 08:04:11 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Vitaly Buka912b6982015-07-06 11:13:03 -07005#include "libweave/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>
16#include <base/files/file_util.h>
17#include <base/logging.h>
18#include <base/message_loop/message_loop.h>
19#include <base/rand_util.h>
20#include <base/strings/string_number_conversions.h>
21#include <base/strings/string_util.h>
22#include <chromeos/data_encoding.h>
23#include <chromeos/key_value_store.h>
24#include <chromeos/strings/string_utils.h>
Vitaly Buka9ba72a82015-08-06 17:36:17 -070025#include "libweave/external/crypto/p224_spake.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -070026#include <gmock/gmock.h>
27#include <gtest/gtest.h>
28
Vitaly Buka912b6982015-07-06 11:13:03 -070029#include "libweave/src/privet/openssl_utils.h"
Vitaly Buka7ce499f2015-06-09 08:04:11 -070030
31using testing::Eq;
32using testing::_;
33
Vitaly Bukab6f015a2015-07-09 14:59:23 -070034namespace weave {
35namespace privet {
Vitaly Buka7ce499f2015-06-09 08:04:11 -070036
37namespace {
38
39bool IsBase64Char(char c) {
40 return isalnum(c) || (c == '+') || (c == '/') || (c == '=');
41}
42
43bool IsBase64(const std::string& text) {
44 return !text.empty() &&
45 !std::any_of(text.begin(), text.end(),
46 std::not1(std::ref(IsBase64Char)));
47}
48
49class MockPairingCallbacks {
50 public:
51 MOCK_METHOD3(OnPairingStart,
52 void(const std::string& session_id,
53 PairingType pairing_type,
54 const std::vector<uint8_t>& code));
55 MOCK_METHOD1(OnPairingEnd, void(const std::string& session_id));
56};
57
58base::FilePath GetTempFilePath() {
59 base::FilePath file_path;
60 EXPECT_TRUE(base::CreateTemporaryFile(&file_path));
61 return file_path;
62}
63
64} // namespace
65
66class SecurityManagerTest : public testing::Test {
67 public:
68 void SetUp() override {
69 chromeos::Blob fingerprint;
70 fingerprint.resize(256 / 8);
71 base::RandBytes(fingerprint.data(), fingerprint.size());
72 security_.SetCertificateFingerprint(fingerprint);
73
74 chromeos::KeyValueStore store;
75 store.SetString("embedded_code", "1234");
76 EXPECT_TRUE(store.Save(embedded_code_path_));
77 }
78
79 void TearDown() override { base::DeleteFile(embedded_code_path_, false); }
80
81 protected:
82 void PairAndAuthenticate(std::string* fingerprint, std::string* signature) {
83 std::string session_id;
84 std::string device_commitment_base64;
85
86 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
87 CryptoType::kSpake_p224, &session_id,
88 &device_commitment_base64, nullptr));
89 EXPECT_FALSE(session_id.empty());
90 EXPECT_FALSE(device_commitment_base64.empty());
91
92 crypto::P224EncryptedKeyExchange spake{
93 crypto::P224EncryptedKeyExchange::kPeerTypeClient, "1234"};
94
95 std::string client_commitment_base64{
96 chromeos::data_encoding::Base64Encode(spake.GetNextMessage())};
97
98 EXPECT_TRUE(security_.ConfirmPairing(session_id, client_commitment_base64,
99 fingerprint, signature, nullptr));
100 EXPECT_TRUE(IsBase64(*fingerprint));
101 EXPECT_TRUE(IsBase64(*signature));
102
103 chromeos::Blob device_commitment;
104 ASSERT_TRUE(chromeos::data_encoding::Base64Decode(device_commitment_base64,
105 &device_commitment));
106 spake.ProcessMessage(
107 chromeos::string_utils::GetBytesAsString(device_commitment));
108
109 chromeos::Blob auth_code{
110 HmacSha256(chromeos::SecureBlob{spake.GetUnverifiedKey()},
111 chromeos::SecureBlob{session_id})};
112
113 std::string auth_code_base64{
114 chromeos::data_encoding::Base64Encode(auth_code)};
115
116 EXPECT_TRUE(security_.IsValidPairingCode(auth_code_base64));
117 }
118
119 const base::Time time_ = base::Time::FromTimeT(1410000000);
120 base::MessageLoop message_loop_;
121 base::FilePath embedded_code_path_{GetTempFilePath()};
122 SecurityManager security_{{PairingType::kEmbeddedCode}, embedded_code_path_};
123};
124
125TEST_F(SecurityManagerTest, IsBase64) {
126 EXPECT_TRUE(IsBase64(
127 security_.CreateAccessToken(UserInfo{AuthScope::kUser, 7}, time_)));
128}
129
130TEST_F(SecurityManagerTest, CreateSameToken) {
131 EXPECT_EQ(
132 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_),
133 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_));
134}
135
136TEST_F(SecurityManagerTest, CreateTokenDifferentScope) {
137 EXPECT_NE(
138 security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}, time_),
139 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_));
140}
141
142TEST_F(SecurityManagerTest, CreateTokenDifferentUser) {
143 EXPECT_NE(
144 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_),
145 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}, time_));
146}
147
148TEST_F(SecurityManagerTest, CreateTokenDifferentTime) {
149 EXPECT_NE(
150 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}, time_),
151 security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567},
152 base::Time::FromTimeT(1400000000)));
153}
154
155TEST_F(SecurityManagerTest, CreateTokenDifferentInstance) {
156 EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_),
157 SecurityManager({}, base::FilePath{})
158 .CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_));
159}
160
161TEST_F(SecurityManagerTest, ParseAccessToken) {
162 // Multiple attempts with random secrets.
163 for (size_t i = 0; i < 1000; ++i) {
164 SecurityManager security{{}, base::FilePath{}};
165
166 std::string token =
167 security.CreateAccessToken(UserInfo{AuthScope::kUser, 5}, time_);
168 base::Time time2;
169 EXPECT_EQ(AuthScope::kUser,
170 security.ParseAccessToken(token, &time2).scope());
171 EXPECT_EQ(5u, security.ParseAccessToken(token, &time2).user_id());
172 // Token timestamp resolution is one second.
173 EXPECT_GE(1, std::abs((time_ - time2).InSeconds()));
174 }
175}
176
177TEST_F(SecurityManagerTest, PairingNoSession) {
178 std::string fingerprint;
179 std::string signature;
180 chromeos::ErrorPtr error;
181 ASSERT_FALSE(
182 security_.ConfirmPairing("123", "345", &fingerprint, &signature, &error));
183 EXPECT_EQ("unknownSession", error->GetCode());
184}
185
186TEST_F(SecurityManagerTest, Pairing) {
187 std::vector<std::pair<std::string, std::string> > fingerprints(2);
188 for (auto& it : fingerprints) {
189 PairAndAuthenticate(&it.first, &it.second);
190 }
191
192 // Same certificate.
193 EXPECT_EQ(fingerprints.front().first, fingerprints.back().first);
194
195 // Signed with different secret.
196 EXPECT_NE(fingerprints.front().second, fingerprints.back().second);
197}
198
199TEST_F(SecurityManagerTest, NotifiesListenersOfSessionStartAndEnd) {
200 testing::StrictMock<MockPairingCallbacks> callbacks;
201 security_.RegisterPairingListeners(
202 base::Bind(&MockPairingCallbacks::OnPairingStart,
203 base::Unretained(&callbacks)),
204 base::Bind(&MockPairingCallbacks::OnPairingEnd,
205 base::Unretained(&callbacks)));
206 for (auto commitment_suffix :
207 std::vector<std::string>{"", "invalid_commitment"}) {
208 // StartPairing should notify us that a new session has begun.
209 std::string session_id;
210 std::string device_commitment;
211 EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
212 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
213 CryptoType::kSpake_p224, &session_id,
214 &device_commitment, nullptr));
215 EXPECT_FALSE(session_id.empty());
216 EXPECT_FALSE(device_commitment.empty());
217 testing::Mock::VerifyAndClearExpectations(&callbacks);
218
219 // ConfirmPairing should notify us that the session has ended.
220 EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
221 crypto::P224EncryptedKeyExchange spake{
222 crypto::P224EncryptedKeyExchange::kPeerTypeServer, "1234"};
223 std::string client_commitment =
224 chromeos::data_encoding::Base64Encode(spake.GetNextMessage());
225 std::string fingerprint, signature;
226 // Regardless of whether the commitment is valid or not, we should get a
227 // callback indicating that the pairing session is gone.
228 security_.ConfirmPairing(session_id, client_commitment + commitment_suffix,
229 &fingerprint, &signature, nullptr);
230 testing::Mock::VerifyAndClearExpectations(&callbacks);
231 }
232}
233
234TEST_F(SecurityManagerTest, CancelPairing) {
235 testing::StrictMock<MockPairingCallbacks> callbacks;
236 security_.RegisterPairingListeners(
237 base::Bind(&MockPairingCallbacks::OnPairingStart,
238 base::Unretained(&callbacks)),
239 base::Bind(&MockPairingCallbacks::OnPairingEnd,
240 base::Unretained(&callbacks)));
241 std::string session_id;
242 std::string device_commitment;
243 EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
244 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
245 CryptoType::kSpake_p224, &session_id,
246 &device_commitment, nullptr));
247 EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
248 EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
249}
250
251TEST_F(SecurityManagerTest, ThrottlePairing) {
252 auto pair = [this]() {
253 std::string session_id;
254 std::string device_commitment;
255 chromeos::ErrorPtr error;
256 bool result = security_.StartPairing(PairingType::kEmbeddedCode,
257 CryptoType::kSpake_p224, &session_id,
258 &device_commitment, &error);
259 EXPECT_TRUE(result || error->GetCode() == "deviceBusy");
260 return result;
261 };
262
263 EXPECT_TRUE(pair());
264 EXPECT_TRUE(pair());
265 EXPECT_TRUE(pair());
266 EXPECT_FALSE(pair());
267 EXPECT_GT(security_.block_pairing_until_, base::Time::Now());
268 EXPECT_LE(security_.block_pairing_until_,
269 base::Time::Now() + base::TimeDelta::FromMinutes(15));
270
271 // Wait timeout.
272 security_.block_pairing_until_ =
273 base::Time::Now() - base::TimeDelta::FromMinutes(1);
274
275 // Allow exactly one attempt.
276 EXPECT_TRUE(pair());
277 EXPECT_FALSE(pair());
278
279 // Wait timeout.
280 security_.block_pairing_until_ =
281 base::Time::Now() - base::TimeDelta::FromMinutes(1);
282
283 // Completely unblock by successfully pairing.
284 std::string fingerprint;
285 std::string signature;
286 PairAndAuthenticate(&fingerprint, &signature);
287
288 // Now we have 3 attempts again.
289 EXPECT_TRUE(pair());
290 EXPECT_TRUE(pair());
291 EXPECT_TRUE(pair());
292 EXPECT_FALSE(pair());
293}
294
295TEST_F(SecurityManagerTest, DontBlockForCanceledSessions) {
296 for (int i = 0; i < 20; ++i) {
297 std::string session_id;
298 std::string device_commitment;
299 EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
300 CryptoType::kSpake_p224, &session_id,
301 &device_commitment, nullptr));
302 EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
303 }
304}
305
306TEST_F(SecurityManagerTest, EmbeddedCodeNotReady) {
307 std::string session_id;
308 std::string device_commitment;
309 base::DeleteFile(embedded_code_path_, false);
310 chromeos::ErrorPtr error;
311 ASSERT_FALSE(security_.StartPairing(PairingType::kEmbeddedCode,
312 CryptoType::kSpake_p224, &session_id,
313 &device_commitment, &error));
314 EXPECT_EQ("deviceBusy", error->GetCode());
315}
316
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700317} // namespace privet
318} // namespace weave