1 // Copyright 2014 The Chromium 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.
5 #include "chrome/browser/local_discovery/privetv3_session.h"
7 #include "base/base64.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "chrome/browser/local_discovery/privet_http.h"
11 #include "content/public/test/test_utils.h"
12 #include "crypto/hmac.h"
13 #include "crypto/p224_spake.h"
14 #include "net/url_request/test_url_fetcher_factory.h"
15 #include "net/url_request/url_request_test_util.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
19 namespace local_discovery
{
23 using testing::InSequence
;
24 using testing::Invoke
;
25 using testing::SaveArg
;
26 using testing::StrictMock
;
29 using PairingType
= PrivetV3Session::PairingType
;
30 using Result
= PrivetV3Session::Result
;
32 const char kInfoResponse
[] =
33 "{\"version\":\"3.0\","
34 "\"endpoints\":{\"httpsPort\": 443},"
35 "\"authentication\":{"
36 " \"mode\":[\"anonymous\",\"pairing\",\"cloud\"],"
37 " \"pairing\":[\"pinCode\",\"embeddedCode\"],"
38 " \"crypto\":[\"p224_spake2\"]"
41 class MockPrivetHTTPClient
: public PrivetHTTPClient
{
43 MockPrivetHTTPClient() {
44 request_context_
= new net::TestURLRequestContextGetter(
45 base::ThreadTaskRunnerHandle::Get());
48 MOCK_METHOD0(GetName
, const std::string
&());
50 CreateInfoOperationPtr
,
51 PrivetJSONOperation
*(const PrivetJSONOperation::ResultCallback
&));
53 void RefreshPrivetToken(
54 const PrivetURLFetcher::TokenCallback
& callback
) override
{
58 scoped_ptr
<PrivetJSONOperation
> CreateInfoOperation(
59 const PrivetJSONOperation::ResultCallback
& callback
) override
{
60 return make_scoped_ptr(CreateInfoOperationPtr(callback
));
63 scoped_ptr
<PrivetURLFetcher
> CreateURLFetcher(
65 net::URLFetcher::RequestType request_type
,
66 PrivetURLFetcher::Delegate
* delegate
) override
{
67 return make_scoped_ptr(new PrivetURLFetcher(
68 url
, request_type
, request_context_
.get(), delegate
));
71 scoped_refptr
<net::TestURLRequestContextGetter
> request_context_
;
76 class PrivetV3SessionTest
: public testing::Test
{
79 : fetcher_factory_(nullptr),
80 session_(make_scoped_ptr(new MockPrivetHTTPClient())) {}
82 ~PrivetV3SessionTest() override
{}
84 void OnInitialized(Result result
, const base::DictionaryValue
& info
) {
85 info_
.MergeDictionary(&info
);
86 OnInitializedMock(result
, info
);
89 MOCK_METHOD2(OnInitializedMock
, void(Result
, const base::DictionaryValue
&));
90 MOCK_METHOD1(OnPairingStarted
, void(Result
));
91 MOCK_METHOD1(OnCodeConfirmed
, void(Result
));
92 MOCK_METHOD2(OnMessageSend
, void(Result
, const base::DictionaryValue
&));
93 MOCK_METHOD1(OnPostData
, void(const base::DictionaryValue
&));
96 void SetUp() override
{
97 EXPECT_CALL(*this, OnInitializedMock(_
, _
)).Times(0);
98 EXPECT_CALL(*this, OnPairingStarted(_
)).Times(0);
99 EXPECT_CALL(*this, OnCodeConfirmed(_
)).Times(0);
100 EXPECT_CALL(*this, OnMessageSend(_
, _
)).Times(0);
101 EXPECT_CALL(*this, OnPostData(_
)).Times(0);
102 session_
.on_post_data_
=
103 base::Bind(&PrivetV3SessionTest::OnPostData
, base::Unretained(this));
106 base::DictionaryValue info_
;
107 base::MessageLoop loop_
;
108 base::Closure quit_closure_
;
109 net::FakeURLFetcherFactory fetcher_factory_
;
110 PrivetV3Session session_
;
113 TEST_F(PrivetV3SessionTest
, InitError
) {
114 EXPECT_CALL(*this, OnInitializedMock(Result::STATUS_CONNECTIONERROR
, _
))
116 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), "",
117 net::HTTP_OK
, net::URLRequestStatus::FAILED
);
119 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
120 base::RunLoop().RunUntilIdle();
123 TEST_F(PrivetV3SessionTest
, VersionError
) {
124 std::string
response(kInfoResponse
);
125 base::ReplaceFirstSubstringAfterOffset(&response
, 0, "3.0", "4.1");
127 EXPECT_CALL(*this, OnInitializedMock(Result::STATUS_SESSIONERROR
, _
))
129 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), response
,
131 net::URLRequestStatus::SUCCESS
);
133 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
134 base::RunLoop().RunUntilIdle();
137 TEST_F(PrivetV3SessionTest
, ModeError
) {
138 std::string
response(kInfoResponse
);
139 base::ReplaceFirstSubstringAfterOffset(&response
, 0, "mode", "mode_");
141 EXPECT_CALL(*this, OnInitializedMock(Result::STATUS_SESSIONERROR
, _
))
143 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), response
,
145 net::URLRequestStatus::SUCCESS
);
147 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
148 base::RunLoop().RunUntilIdle();
151 TEST_F(PrivetV3SessionTest
, Pairing
) {
152 EXPECT_CALL(*this, OnInitializedMock(Result::STATUS_SUCCESS
, _
))
154 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"),
155 kInfoResponse
, net::HTTP_OK
,
156 net::URLRequestStatus::SUCCESS
);
159 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
160 base::RunLoop().RunUntilIdle();
162 const base::ListValue
* pairing
= nullptr;
163 ASSERT_TRUE(info_
.GetList("authentication.pairing", &pairing
));
165 std::string pairing_string
;
166 ASSERT_TRUE(pairing
->GetString(0, &pairing_string
));
167 EXPECT_EQ("pinCode", pairing_string
);
169 ASSERT_TRUE(pairing
->GetString(1, &pairing_string
));
170 EXPECT_EQ("embeddedCode", pairing_string
);
172 crypto::P224EncryptedKeyExchange
spake(
173 crypto::P224EncryptedKeyExchange::kPeerTypeServer
, "testPin");
175 EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS
)).Times(1);
176 EXPECT_CALL(*this, OnPostData(_
))
178 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
179 std::string pairing_type
;
180 EXPECT_TRUE(data
.GetString("pairing", &pairing_type
));
181 EXPECT_EQ("embeddedCode", pairing_type
);
183 std::string crypto_type
;
184 EXPECT_TRUE(data
.GetString("crypto", &crypto_type
));
185 EXPECT_EQ("p224_spake2", crypto_type
);
187 std::string device_commitment
;
188 base::Base64Encode(spake
.GetNextMessage(), &device_commitment
);
189 fetcher_factory_
.SetFakeResponse(
190 GURL("http://host/privet/v3/pairing/start"),
192 "{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
193 device_commitment
.c_str()),
194 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
196 session_
.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE
,
197 base::Bind(&PrivetV3SessionTest::OnPairingStarted
,
198 base::Unretained(this)));
199 base::RunLoop().RunUntilIdle();
201 EXPECT_TRUE(session_
.fingerprint_
.empty());
202 EXPECT_EQ("Privet anonymous", session_
.privet_auth_token_
);
204 EXPECT_CALL(*this, OnCodeConfirmed(Result::STATUS_SUCCESS
)).Times(1);
205 InSequence in_sequence
;
206 EXPECT_CALL(*this, OnPostData(_
))
208 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
209 std::string commitment_base64
;
210 EXPECT_TRUE(data
.GetString("clientCommitment", &commitment_base64
));
211 std::string commitment
;
212 EXPECT_TRUE(base::Base64Decode(commitment_base64
, &commitment
));
214 std::string session_id
;
215 EXPECT_TRUE(data
.GetString("sessionId", &session_id
));
216 EXPECT_EQ("testId", session_id
);
218 EXPECT_EQ(spake
.ProcessMessage(commitment
),
219 crypto::P224EncryptedKeyExchange::kResultPending
);
221 std::string
fingerprint("testFinterprint");
222 std::string fingerprint_base64
;
223 base::Base64Encode(fingerprint
, &fingerprint_base64
);
225 crypto::HMAC
hmac(crypto::HMAC::SHA256
);
226 const std::string
& key
= spake
.GetUnverifiedKey();
227 EXPECT_TRUE(hmac
.Init(key
));
228 std::string
signature(hmac
.DigestLength(), ' ');
229 EXPECT_TRUE(hmac
.Sign(fingerprint
, reinterpret_cast<unsigned char*>(
230 string_as_array(&signature
)),
233 std::string signature_base64
;
234 base::Base64Encode(signature
, &signature_base64
);
236 fetcher_factory_
.SetFakeResponse(
237 GURL("http://host/privet/v3/pairing/confirm"),
239 "{\"certFingerprint\":\"%s\",\"certSignature\":\"%s\"}",
240 fingerprint_base64
.c_str(), signature_base64
.c_str()),
241 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
243 EXPECT_CALL(*this, OnPostData(_
))
245 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
246 std::string access_token_base64
;
247 EXPECT_TRUE(data
.GetString("authCode", &access_token_base64
));
248 std::string access_token
;
249 EXPECT_TRUE(base::Base64Decode(access_token_base64
, &access_token
));
251 crypto::HMAC
hmac(crypto::HMAC::SHA256
);
252 const std::string
& key
= spake
.GetUnverifiedKey();
253 EXPECT_TRUE(hmac
.Init(key
));
254 EXPECT_TRUE(hmac
.Verify("testId", access_token
));
256 fetcher_factory_
.SetFakeResponse(
257 GURL("http://host/privet/v3/auth"),
258 "{\"accessToken\":\"567\",\"tokenType\":\"testType\","
259 "\"scope\":\"owner\"}",
260 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
262 session_
.ConfirmCode("testPin",
263 base::Bind(&PrivetV3SessionTest::OnCodeConfirmed
,
264 base::Unretained(this)));
265 base::RunLoop().RunUntilIdle();
267 EXPECT_FALSE(session_
.fingerprint_
.empty());
268 EXPECT_EQ("testType 567", session_
.privet_auth_token_
);
271 TEST_F(PrivetV3SessionTest
, Cancel
) {
272 EXPECT_CALL(*this, OnInitializedMock(Result::STATUS_SUCCESS
, _
)).Times(1);;
273 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"),
274 kInfoResponse
, net::HTTP_OK
,
275 net::URLRequestStatus::SUCCESS
);
278 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
279 base::RunLoop().RunUntilIdle();
281 EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS
)).Times(1);
282 EXPECT_CALL(*this, OnPostData(_
))
283 .WillOnce(testing::Invoke([this](const base::DictionaryValue
& data
) {
284 std::string device_commitment
;
285 base::Base64Encode("1234", &device_commitment
);
286 fetcher_factory_
.SetFakeResponse(
287 GURL("http://host/privet/v3/pairing/start"),
289 "{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
290 device_commitment
.c_str()),
291 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
293 session_
.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE
,
294 base::Bind(&PrivetV3SessionTest::OnPairingStarted
,
295 base::Unretained(this)));
296 base::RunLoop().RunUntilIdle();
298 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/v3/pairing/cancel"),
299 kInfoResponse
, net::HTTP_OK
,
300 net::URLRequestStatus::SUCCESS
);
301 EXPECT_CALL(*this, OnPostData(_
))
302 .WillOnce(testing::Invoke([this](const base::DictionaryValue
& data
) {
303 std::string session_id
;
304 EXPECT_TRUE(data
.GetString("sessionId", &session_id
));
308 // TODO(vitalybuka): replace PrivetHTTPClient with regular URL fetcher and
309 // implement SendMessage test.
311 } // namespace local_discovery