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 "chrome/browser/local_discovery/privet_http.h"
10 #include "content/public/test/test_utils.h"
11 #include "crypto/hmac.h"
12 #include "crypto/p224_spake.h"
13 #include "net/url_request/test_url_fetcher_factory.h"
14 #include "net/url_request/url_request_test_util.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
18 namespace local_discovery
{
22 using testing::InSequence
;
23 using testing::Invoke
;
24 using testing::SaveArg
;
25 using testing::StrictMock
;
28 using PairingType
= PrivetV3Session::PairingType
;
29 using Result
= PrivetV3Session::Result
;
31 const char kInfoResponse
[] =
32 "{\"version\":\"3.0\","
33 "\"endpoints\":{\"httpsPort\": 443},"
34 "\"authentication\":{"
35 " \"mode\":[\"anonymous\",\"pairing\",\"cloud\"],"
36 " \"pairing\":[\"pinCode\",\"embeddedCode\"],"
37 " \"crypto\":[\"p224_spake2\"]"
40 class MockPrivetHTTPClient
: public PrivetHTTPClient
{
42 MockPrivetHTTPClient() {
44 new net::TestURLRequestContextGetter(base::MessageLoopProxy::current());
47 MOCK_METHOD0(GetName
, const std::string
&());
49 CreateInfoOperationPtr
,
50 PrivetJSONOperation
*(const PrivetJSONOperation::ResultCallback
&));
52 virtual void RefreshPrivetToken(
53 const PrivetURLFetcher::TokenCallback
& callback
) override
{
57 virtual scoped_ptr
<PrivetJSONOperation
> CreateInfoOperation(
58 const PrivetJSONOperation::ResultCallback
& callback
) override
{
59 return make_scoped_ptr(CreateInfoOperationPtr(callback
));
62 virtual scoped_ptr
<PrivetURLFetcher
> CreateURLFetcher(
64 net::URLFetcher::RequestType request_type
,
65 PrivetURLFetcher::Delegate
* delegate
) override
{
66 return make_scoped_ptr(new PrivetURLFetcher(
67 url
, request_type
, request_context_
.get(), delegate
));
70 scoped_refptr
<net::TestURLRequestContextGetter
> request_context_
;
75 class PrivetV3SessionTest
: public testing::Test
{
78 : fetcher_factory_(nullptr),
79 session_(make_scoped_ptr(new MockPrivetHTTPClient())) {}
81 virtual ~PrivetV3SessionTest() {}
83 MOCK_METHOD2(OnInitialized
, void(Result
, const std::vector
<PairingType
>&));
84 MOCK_METHOD1(OnPairingStarted
, void(Result
));
85 MOCK_METHOD1(OnCodeConfirmed
, void(Result
));
86 MOCK_METHOD2(OnMessageSend
, void(Result
, const base::DictionaryValue
& value
));
87 MOCK_METHOD1(OnPostData
, void(const base::DictionaryValue
& data
));
90 virtual void SetUp() override
{
91 EXPECT_CALL(*this, OnInitialized(_
, _
)).Times(0);
92 EXPECT_CALL(*this, OnPairingStarted(_
)).Times(0);
93 EXPECT_CALL(*this, OnCodeConfirmed(_
)).Times(0);
94 EXPECT_CALL(*this, OnMessageSend(_
, _
)).Times(0);
95 EXPECT_CALL(*this, OnPostData(_
)).Times(0);
96 session_
.on_post_data_
=
97 base::Bind(&PrivetV3SessionTest::OnPostData
, base::Unretained(this));
100 base::MessageLoop loop_
;
101 base::Closure quit_closure_
;
102 net::FakeURLFetcherFactory fetcher_factory_
;
103 PrivetV3Session session_
;
106 TEST_F(PrivetV3SessionTest
, InitError
) {
107 EXPECT_CALL(*this, OnInitialized(Result::STATUS_CONNECTIONERROR
, _
)).Times(1);
108 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), "",
109 net::HTTP_OK
, net::URLRequestStatus::FAILED
);
111 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
112 base::RunLoop().RunUntilIdle();
115 TEST_F(PrivetV3SessionTest
, VersionError
) {
116 std::string
response(kInfoResponse
);
117 ReplaceFirstSubstringAfterOffset(&response
, 0, "3.0", "4.1");
119 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR
, _
)).Times(1);
120 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), response
,
122 net::URLRequestStatus::SUCCESS
);
124 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
125 base::RunLoop().RunUntilIdle();
128 TEST_F(PrivetV3SessionTest
, ModeError
) {
129 std::string
response(kInfoResponse
);
130 ReplaceFirstSubstringAfterOffset(&response
, 0, "mode", "mode_");
132 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR
, _
)).Times(1);
133 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"), response
,
135 net::URLRequestStatus::SUCCESS
);
137 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
138 base::RunLoop().RunUntilIdle();
141 TEST_F(PrivetV3SessionTest
, Pairing
) {
142 std::vector
<PairingType
> pairings
;
143 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SUCCESS
, _
))
144 .WillOnce(SaveArg
<1>(&pairings
));
145 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"),
146 kInfoResponse
, net::HTTP_OK
,
147 net::URLRequestStatus::SUCCESS
);
150 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
151 base::RunLoop().RunUntilIdle();
153 EXPECT_EQ(2u, pairings
.size());
154 EXPECT_EQ(PairingType::PAIRING_TYPE_PINCODE
, pairings
[0]);
155 EXPECT_EQ(PairingType::PAIRING_TYPE_EMBEDDEDCODE
, pairings
[1]);
157 crypto::P224EncryptedKeyExchange
spake(
158 crypto::P224EncryptedKeyExchange::kPeerTypeServer
, "testPin");
160 EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS
)).Times(1);
161 EXPECT_CALL(*this, OnPostData(_
))
163 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
164 std::string pairing_type
;
165 EXPECT_TRUE(data
.GetString("pairing", &pairing_type
));
166 EXPECT_EQ("embeddedCode", pairing_type
);
168 std::string crypto_type
;
169 EXPECT_TRUE(data
.GetString("crypto", &crypto_type
));
170 EXPECT_EQ("p224_spake2", crypto_type
);
172 std::string device_commitment
;
173 base::Base64Encode(spake
.GetNextMessage(), &device_commitment
);
174 fetcher_factory_
.SetFakeResponse(
175 GURL("http://host/privet/v3/pairing/start"),
177 "{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
178 device_commitment
.c_str()),
179 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
181 session_
.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE
,
182 base::Bind(&PrivetV3SessionTest::OnPairingStarted
,
183 base::Unretained(this)));
184 base::RunLoop().RunUntilIdle();
186 EXPECT_TRUE(session_
.fingerprint_
.empty());
187 EXPECT_EQ("Privet anonymous", session_
.privet_auth_token_
);
189 EXPECT_CALL(*this, OnCodeConfirmed(Result::STATUS_SUCCESS
)).Times(1);
190 InSequence in_sequence
;
191 EXPECT_CALL(*this, OnPostData(_
))
193 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
194 std::string commitment_base64
;
195 EXPECT_TRUE(data
.GetString("clientCommitment", &commitment_base64
));
196 std::string commitment
;
197 EXPECT_TRUE(base::Base64Decode(commitment_base64
, &commitment
));
199 std::string session_id
;
200 EXPECT_TRUE(data
.GetString("sessionId", &session_id
));
201 EXPECT_EQ("testId", session_id
);
203 EXPECT_EQ(spake
.ProcessMessage(commitment
),
204 crypto::P224EncryptedKeyExchange::kResultPending
);
206 std::string
fingerprint("testFinterprint");
207 std::string fingerprint_base64
;
208 base::Base64Encode(fingerprint
, &fingerprint_base64
);
210 crypto::HMAC
hmac(crypto::HMAC::SHA256
);
211 const std::string
& key
= spake
.GetUnverifiedKey();
212 EXPECT_TRUE(hmac
.Init(key
));
213 std::string
signature(hmac
.DigestLength(), ' ');
214 EXPECT_TRUE(hmac
.Sign(fingerprint
, reinterpret_cast<unsigned char*>(
215 string_as_array(&signature
)),
218 std::string signature_base64
;
219 base::Base64Encode(signature
, &signature_base64
);
221 fetcher_factory_
.SetFakeResponse(
222 GURL("http://host/privet/v3/pairing/confirm"),
224 "{\"certFingerprint\":\"%s\",\"certSignature\":\"%s\"}",
225 fingerprint_base64
.c_str(), signature_base64
.c_str()),
226 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
228 EXPECT_CALL(*this, OnPostData(_
))
230 testing::Invoke([this, &spake
](const base::DictionaryValue
& data
) {
231 std::string access_token_base64
;
232 EXPECT_TRUE(data
.GetString("authCode", &access_token_base64
));
233 std::string access_token
;
234 EXPECT_TRUE(base::Base64Decode(access_token_base64
, &access_token
));
236 crypto::HMAC
hmac(crypto::HMAC::SHA256
);
237 const std::string
& key
= spake
.GetUnverifiedKey();
238 EXPECT_TRUE(hmac
.Init(key
));
239 EXPECT_TRUE(hmac
.Verify("testId", access_token
));
241 fetcher_factory_
.SetFakeResponse(
242 GURL("http://host/privet/v3/auth"),
243 "{\"accessToken\":\"567\",\"tokenType\":\"testType\","
244 "\"scope\":\"owner\"}",
245 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
247 session_
.ConfirmCode("testPin",
248 base::Bind(&PrivetV3SessionTest::OnCodeConfirmed
,
249 base::Unretained(this)));
250 base::RunLoop().RunUntilIdle();
252 EXPECT_FALSE(session_
.fingerprint_
.empty());
253 EXPECT_EQ("testType 567", session_
.privet_auth_token_
);
256 TEST_F(PrivetV3SessionTest
, Cancel
) {
257 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SUCCESS
, _
));
258 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/info"),
259 kInfoResponse
, net::HTTP_OK
,
260 net::URLRequestStatus::SUCCESS
);
263 base::Bind(&PrivetV3SessionTest::OnInitialized
, base::Unretained(this)));
264 base::RunLoop().RunUntilIdle();
266 EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS
)).Times(1);
267 EXPECT_CALL(*this, OnPostData(_
))
268 .WillOnce(testing::Invoke([this](const base::DictionaryValue
& data
) {
269 std::string device_commitment
;
270 base::Base64Encode("1234", &device_commitment
);
271 fetcher_factory_
.SetFakeResponse(
272 GURL("http://host/privet/v3/pairing/start"),
274 "{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
275 device_commitment
.c_str()),
276 net::HTTP_OK
, net::URLRequestStatus::SUCCESS
);
278 session_
.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE
,
279 base::Bind(&PrivetV3SessionTest::OnPairingStarted
,
280 base::Unretained(this)));
281 base::RunLoop().RunUntilIdle();
283 fetcher_factory_
.SetFakeResponse(GURL("http://host/privet/v3/pairing/cancel"),
284 kInfoResponse
, net::HTTP_OK
,
285 net::URLRequestStatus::SUCCESS
);
286 EXPECT_CALL(*this, OnPostData(_
))
287 .WillOnce(testing::Invoke([this](const base::DictionaryValue
& data
) {
288 std::string session_id
;
289 EXPECT_TRUE(data
.GetString("sessionId", &session_id
));
293 // TODO(vitalybuka): replace PrivetHTTPClient with regular URL fetcher and
294 // implement SendMessage test.
296 } // namespace local_discovery