OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/local_discovery/privetv3_session.h" | 5 #include "chrome/browser/local_discovery/privetv3_session.h" |
6 | 6 |
| 7 #include "base/base64.h" |
| 8 #include "base/strings/stringprintf.h" |
7 #include "chrome/browser/local_discovery/privet_http.h" | 9 #include "chrome/browser/local_discovery/privet_http.h" |
8 #include "content/public/test/test_utils.h" | 10 #include "content/public/test/test_utils.h" |
| 11 #include "crypto/hmac.h" |
| 12 #include "crypto/p224_spake.h" |
9 #include "net/url_request/test_url_fetcher_factory.h" | 13 #include "net/url_request/test_url_fetcher_factory.h" |
| 14 #include "net/url_request/url_request_test_util.h" |
10 #include "testing/gmock/include/gmock/gmock.h" | 15 #include "testing/gmock/include/gmock/gmock.h" |
11 #include "testing/gtest/include/gtest/gtest.h" | 16 #include "testing/gtest/include/gtest/gtest.h" |
12 | 17 |
13 namespace local_discovery { | 18 namespace local_discovery { |
14 | 19 |
15 namespace { | 20 namespace { |
16 | 21 |
17 using testing::Invoke; | 22 using testing::Invoke; |
18 using testing::InvokeWithoutArgs; | 23 using testing::SaveArg; |
19 using testing::StrictMock; | 24 using testing::StrictMock; |
20 using testing::_; | 25 using testing::_; |
21 | 26 |
| 27 using PairingType = PrivetV3Session::PairingType; |
| 28 using Result = PrivetV3Session::Result; |
| 29 |
| 30 const char kInfoResponse[] = |
| 31 "{\"version\":\"3.0\"," |
| 32 "\"endpoints\":{\"httpsPort\": 443}," |
| 33 "\"authentication\":{" |
| 34 " \"mode\":[\"anonymous\",\"pairing\",\"cloud\"]," |
| 35 " \"pairing\":[\"pinCode\",\"embeddedCode\"]," |
| 36 " \"crypto\":[\"p224_spake2\"]" |
| 37 "}}"; |
| 38 |
| 39 class MockPrivetHTTPClient : public PrivetHTTPClient { |
| 40 public: |
| 41 MockPrivetHTTPClient() { |
| 42 request_context_ = |
| 43 new net::TestURLRequestContextGetter(base::MessageLoopProxy::current()); |
| 44 } |
| 45 |
| 46 MOCK_METHOD0(GetName, const std::string&()); |
| 47 MOCK_METHOD1( |
| 48 CreateInfoOperationPtr, |
| 49 PrivetJSONOperation*(const PrivetJSONOperation::ResultCallback&)); |
| 50 |
| 51 virtual void RefreshPrivetToken( |
| 52 const PrivetURLFetcher::TokenCallback& callback) override { |
| 53 FAIL(); |
| 54 } |
| 55 |
| 56 virtual scoped_ptr<PrivetJSONOperation> CreateInfoOperation( |
| 57 const PrivetJSONOperation::ResultCallback& callback) override { |
| 58 return make_scoped_ptr(CreateInfoOperationPtr(callback)); |
| 59 } |
| 60 |
| 61 virtual scoped_ptr<PrivetURLFetcher> CreateURLFetcher( |
| 62 const GURL& url, |
| 63 net::URLFetcher::RequestType request_type, |
| 64 PrivetURLFetcher::Delegate* delegate) override { |
| 65 return make_scoped_ptr(new PrivetURLFetcher( |
| 66 url, request_type, request_context_.get(), delegate)); |
| 67 } |
| 68 |
| 69 scoped_refptr<net::TestURLRequestContextGetter> request_context_; |
| 70 }; |
| 71 |
| 72 } // namespace |
| 73 |
22 class PrivetV3SessionTest : public testing::Test { | 74 class PrivetV3SessionTest : public testing::Test { |
23 public: | 75 public: |
24 PrivetV3SessionTest() : session_(scoped_ptr<PrivetHTTPClient>()) {} | 76 PrivetV3SessionTest() |
| 77 : fetcher_factory_(nullptr), |
| 78 session_(make_scoped_ptr(new MockPrivetHTTPClient())) {} |
25 | 79 |
26 virtual ~PrivetV3SessionTest() {} | 80 virtual ~PrivetV3SessionTest() {} |
27 | 81 |
28 MOCK_METHOD2(OnInitialized, | 82 MOCK_METHOD2(OnInitialized, void(Result, const std::vector<PairingType>&)); |
29 void(PrivetV3Session::Result, | 83 MOCK_METHOD1(OnPairingStarted, void(Result)); |
30 const std::vector<PrivetV3Session::PairingType>&)); | 84 MOCK_METHOD1(OnCodeConfirmed, void(Result)); |
31 MOCK_METHOD1(OnPairingStarted, void(PrivetV3Session::Result)); | 85 MOCK_METHOD2(OnMessageSend, void(Result, const base::DictionaryValue& value)); |
32 MOCK_METHOD1(OnCodeConfirmed, void(PrivetV3Session::Result)); | 86 MOCK_METHOD1(OnPostData, void(const base::DictionaryValue& data)); |
33 MOCK_METHOD2(OnMessageSend, | |
34 void(PrivetV3Session::Result, | |
35 const base::DictionaryValue& value)); | |
36 | 87 |
37 protected: | 88 protected: |
38 virtual void SetUp() override { | 89 virtual void SetUp() override { |
39 EXPECT_CALL(*this, OnInitialized(_, _)).Times(0); | 90 EXPECT_CALL(*this, OnInitialized(_, _)).Times(0); |
40 EXPECT_CALL(*this, OnPairingStarted(_)).Times(0); | 91 EXPECT_CALL(*this, OnPairingStarted(_)).Times(0); |
41 EXPECT_CALL(*this, OnCodeConfirmed(_)).Times(0); | 92 EXPECT_CALL(*this, OnCodeConfirmed(_)).Times(0); |
42 } | 93 EXPECT_CALL(*this, OnMessageSend(_, _)).Times(0); |
43 | 94 EXPECT_CALL(*this, OnPostData(_)).Times(0); |
44 PrivetV3Session session_; | 95 session_.on_post_data_ = |
| 96 base::Bind(&PrivetV3SessionTest::OnPostData, base::Unretained(this)); |
| 97 } |
| 98 |
45 base::MessageLoop loop_; | 99 base::MessageLoop loop_; |
46 base::Closure quit_closure_; | 100 base::Closure quit_closure_; |
| 101 net::FakeURLFetcherFactory fetcher_factory_; |
| 102 PrivetV3Session session_; |
47 }; | 103 }; |
48 | 104 |
| 105 TEST_F(PrivetV3SessionTest, InitError) { |
| 106 EXPECT_CALL(*this, OnInitialized(Result::STATUS_CONNECTIONERROR, _)).Times(1); |
| 107 fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), "", |
| 108 net::HTTP_OK, net::URLRequestStatus::FAILED); |
| 109 session_.Init( |
| 110 base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this))); |
| 111 base::RunLoop().RunUntilIdle(); |
| 112 } |
| 113 |
| 114 TEST_F(PrivetV3SessionTest, VersionError) { |
| 115 std::string response(kInfoResponse); |
| 116 ReplaceFirstSubstringAfterOffset(&response, 0, "3.0", "4.1"); |
| 117 |
| 118 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR, _)).Times(1); |
| 119 fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), response, |
| 120 net::HTTP_OK, |
| 121 net::URLRequestStatus::SUCCESS); |
| 122 session_.Init( |
| 123 base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this))); |
| 124 base::RunLoop().RunUntilIdle(); |
| 125 } |
| 126 |
| 127 TEST_F(PrivetV3SessionTest, ModeError) { |
| 128 std::string response(kInfoResponse); |
| 129 ReplaceFirstSubstringAfterOffset(&response, 0, "mode", "mode_"); |
| 130 |
| 131 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR, _)).Times(1); |
| 132 fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), response, |
| 133 net::HTTP_OK, |
| 134 net::URLRequestStatus::SUCCESS); |
| 135 session_.Init( |
| 136 base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this))); |
| 137 base::RunLoop().RunUntilIdle(); |
| 138 } |
| 139 |
49 TEST_F(PrivetV3SessionTest, Pairing) { | 140 TEST_F(PrivetV3SessionTest, Pairing) { |
50 { | 141 std::vector<PairingType> pairings; |
51 base::RunLoop run_loop; | 142 EXPECT_CALL(*this, OnInitialized(Result::STATUS_SUCCESS, _)) |
52 EXPECT_CALL(*this, | 143 .WillOnce(SaveArg<1>(&pairings)); |
53 OnInitialized(PrivetV3Session::Result::STATUS_SUCCESS, _)) | 144 fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), |
54 .Times(1) | 145 kInfoResponse, net::HTTP_OK, |
55 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | 146 net::URLRequestStatus::SUCCESS); |
56 session_.Init(base::Bind(&PrivetV3SessionTest::OnInitialized, | 147 |
57 base::Unretained(this))); | 148 session_.Init( |
58 run_loop.Run(); | 149 base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this))); |
59 } | 150 base::RunLoop().RunUntilIdle(); |
60 | 151 |
61 { | 152 EXPECT_EQ(2u, pairings.size()); |
62 base::RunLoop run_loop; | 153 EXPECT_EQ(PairingType::PAIRING_TYPE_PINCODE, pairings[0]); |
63 EXPECT_CALL(*this, | 154 EXPECT_EQ(PairingType::PAIRING_TYPE_EMBEDDEDCODE, pairings[1]); |
64 OnPairingStarted(PrivetV3Session::Result::STATUS_SUCCESS)) | 155 |
65 .Times(1) | 156 crypto::P224EncryptedKeyExchange spake( |
66 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | 157 crypto::P224EncryptedKeyExchange::kPeerTypeServer, "testPin"); |
67 session_.StartPairing(PrivetV3Session::PairingType::PAIRING_TYPE_PINCODE, | 158 |
68 base::Bind(&PrivetV3SessionTest::OnPairingStarted, | 159 EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS)).Times(1); |
69 base::Unretained(this))); | 160 EXPECT_CALL(*this, OnPostData(_)) |
70 run_loop.Run(); | 161 .WillOnce( |
71 } | 162 testing::Invoke([this, &spake](const base::DictionaryValue& data) { |
72 | 163 std::string pairing_type; |
73 { | 164 EXPECT_TRUE(data.GetString("pairing", &pairing_type)); |
74 base::RunLoop run_loop; | 165 EXPECT_EQ("embeddedCode", pairing_type); |
75 EXPECT_CALL(*this, OnCodeConfirmed(PrivetV3Session::Result::STATUS_SUCCESS)) | 166 |
76 .Times(1) | 167 std::string crypto_type; |
77 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | 168 EXPECT_TRUE(data.GetString("crypto", &crypto_type)); |
78 session_.ConfirmCode("1234", | 169 EXPECT_EQ("p224_spake2", crypto_type); |
79 base::Bind(&PrivetV3SessionTest::OnCodeConfirmed, | 170 |
80 base::Unretained(this))); | 171 std::string device_commitment; |
81 run_loop.Run(); | 172 base::Base64Encode(spake.GetNextMessage(), &device_commitment); |
82 } | 173 fetcher_factory_.SetFakeResponse( |
| 174 GURL("http://host/privet/v3/pairing/start"), |
| 175 base::StringPrintf( |
| 176 "{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}", |
| 177 device_commitment.c_str()), |
| 178 net::HTTP_OK, net::URLRequestStatus::SUCCESS); |
| 179 })); |
| 180 session_.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE, |
| 181 base::Bind(&PrivetV3SessionTest::OnPairingStarted, |
| 182 base::Unretained(this))); |
| 183 base::RunLoop().RunUntilIdle(); |
| 184 |
| 185 EXPECT_CALL(*this, OnCodeConfirmed(Result::STATUS_SUCCESS)).Times(1); |
| 186 EXPECT_CALL(*this, OnPostData(_)) |
| 187 .WillOnce( |
| 188 testing::Invoke([this, &spake](const base::DictionaryValue& data) { |
| 189 std::string commitment_base64; |
| 190 EXPECT_TRUE(data.GetString("clientCommitment", &commitment_base64)); |
| 191 std::string commitment; |
| 192 EXPECT_TRUE(base::Base64Decode(commitment_base64, &commitment)); |
| 193 |
| 194 std::string session_id; |
| 195 EXPECT_TRUE(data.GetString("sessionId", &session_id)); |
| 196 EXPECT_EQ("testId", session_id); |
| 197 |
| 198 EXPECT_EQ(spake.ProcessMessage(commitment), |
| 199 crypto::P224EncryptedKeyExchange::kResultPending); |
| 200 |
| 201 std::string fingerprint("testFinterprint"); |
| 202 std::string fingerprint_base64; |
| 203 base::Base64Encode(fingerprint, &fingerprint_base64); |
| 204 |
| 205 crypto::HMAC hmac(crypto::HMAC::SHA256); |
| 206 const std::string& key = spake.GetUnverifiedKey(); |
| 207 EXPECT_TRUE(hmac.Init(key)); |
| 208 std::string signature(hmac.DigestLength(), ' '); |
| 209 EXPECT_TRUE(hmac.Sign(fingerprint, reinterpret_cast<unsigned char*>( |
| 210 string_as_array(&signature)), |
| 211 signature.size())); |
| 212 |
| 213 std::string signature_base64; |
| 214 base::Base64Encode(signature, &signature_base64); |
| 215 |
| 216 fetcher_factory_.SetFakeResponse( |
| 217 GURL("http://host/privet/v3/pairing/confirm"), |
| 218 base::StringPrintf( |
| 219 "{\"certFingerprint\":\"%s\",\"certSignature\":\"%s\"}", |
| 220 fingerprint_base64.c_str(), signature_base64.c_str()), |
| 221 net::HTTP_OK, net::URLRequestStatus::SUCCESS); |
| 222 })); |
| 223 session_.ConfirmCode("testPin", |
| 224 base::Bind(&PrivetV3SessionTest::OnCodeConfirmed, |
| 225 base::Unretained(this))); |
| 226 base::RunLoop().RunUntilIdle(); |
83 } | 227 } |
84 | 228 |
85 // TODO(vitalybuka): replace PrivetHTTPClient with regular URL fetcher and | 229 // TODO(vitalybuka): replace PrivetHTTPClient with regular URL fetcher and |
86 // implement SendMessage test. | 230 // implement SendMessage test. |
87 | 231 |
88 } // namespace | |
89 | |
90 } // namespace local_discovery | 232 } // namespace local_discovery |
OLD | NEW |