blob: 8cf8e89ebdfe851fcdfe4ab1f473150c966b73c5 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/local_discovery/privetv3_session.h"
#include "base/base64.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/local_discovery/privet_http.h"
#include "content/public/test/test_utils.h"
#include "crypto/hmac.h"
#include "crypto/p224_spake.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace local_discovery {
namespace {
using testing::InSequence;
using testing::Invoke;
using testing::SaveArg;
using testing::StrictMock;
using testing::_;
using PairingType = PrivetV3Session::PairingType;
using Result = PrivetV3Session::Result;
const char kInfoResponse[] =
"{\"version\":\"3.0\","
"\"endpoints\":{\"httpsPort\": 443},"
"\"authentication\":{"
" \"mode\":[\"anonymous\",\"pairing\",\"cloud\"],"
" \"pairing\":[\"pinCode\",\"embeddedCode\"],"
" \"crypto\":[\"p224_spake2\"]"
"}}";
class MockPrivetHTTPClient : public PrivetHTTPClient {
public:
MockPrivetHTTPClient() {
request_context_ =
new net::TestURLRequestContextGetter(base::MessageLoopProxy::current());
}
MOCK_METHOD0(GetName, const std::string&());
MOCK_METHOD1(
CreateInfoOperationPtr,
PrivetJSONOperation*(const PrivetJSONOperation::ResultCallback&));
void RefreshPrivetToken(
const PrivetURLFetcher::TokenCallback& callback) override {
FAIL();
}
scoped_ptr<PrivetJSONOperation> CreateInfoOperation(
const PrivetJSONOperation::ResultCallback& callback) override {
return make_scoped_ptr(CreateInfoOperationPtr(callback));
}
scoped_ptr<PrivetURLFetcher> CreateURLFetcher(
const GURL& url,
net::URLFetcher::RequestType request_type,
PrivetURLFetcher::Delegate* delegate) override {
return make_scoped_ptr(new PrivetURLFetcher(
url, request_type, request_context_.get(), delegate));
}
scoped_refptr<net::TestURLRequestContextGetter> request_context_;
};
} // namespace
class PrivetV3SessionTest : public testing::Test {
public:
PrivetV3SessionTest()
: fetcher_factory_(nullptr),
session_(make_scoped_ptr(new MockPrivetHTTPClient())) {}
~PrivetV3SessionTest() override {}
MOCK_METHOD2(OnInitialized, void(Result, const std::vector<PairingType>&));
MOCK_METHOD1(OnPairingStarted, void(Result));
MOCK_METHOD1(OnCodeConfirmed, void(Result));
MOCK_METHOD2(OnMessageSend, void(Result, const base::DictionaryValue& value));
MOCK_METHOD1(OnPostData, void(const base::DictionaryValue& data));
protected:
void SetUp() override {
EXPECT_CALL(*this, OnInitialized(_, _)).Times(0);
EXPECT_CALL(*this, OnPairingStarted(_)).Times(0);
EXPECT_CALL(*this, OnCodeConfirmed(_)).Times(0);
EXPECT_CALL(*this, OnMessageSend(_, _)).Times(0);
EXPECT_CALL(*this, OnPostData(_)).Times(0);
session_.on_post_data_ =
base::Bind(&PrivetV3SessionTest::OnPostData, base::Unretained(this));
}
base::MessageLoop loop_;
base::Closure quit_closure_;
net::FakeURLFetcherFactory fetcher_factory_;
PrivetV3Session session_;
};
TEST_F(PrivetV3SessionTest, InitError) {
EXPECT_CALL(*this, OnInitialized(Result::STATUS_CONNECTIONERROR, _)).Times(1);
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), "",
net::HTTP_OK, net::URLRequestStatus::FAILED);
session_.Init(
base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
TEST_F(PrivetV3SessionTest, VersionError) {
std::string response(kInfoResponse);
ReplaceFirstSubstringAfterOffset(&response, 0, "3.0", "4.1");
EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR, _)).Times(1);
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), response,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
session_.Init(
base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
TEST_F(PrivetV3SessionTest, ModeError) {
std::string response(kInfoResponse);
ReplaceFirstSubstringAfterOffset(&response, 0, "mode", "mode_");
EXPECT_CALL(*this, OnInitialized(Result::STATUS_SESSIONERROR, _)).Times(1);
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"), response,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
session_.Init(
base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
TEST_F(PrivetV3SessionTest, Pairing) {
std::vector<PairingType> pairings;
EXPECT_CALL(*this, OnInitialized(Result::STATUS_SUCCESS, _))
.WillOnce(SaveArg<1>(&pairings));
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"),
kInfoResponse, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
session_.Init(
base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2u, pairings.size());
EXPECT_EQ(PairingType::PAIRING_TYPE_PINCODE, pairings[0]);
EXPECT_EQ(PairingType::PAIRING_TYPE_EMBEDDEDCODE, pairings[1]);
crypto::P224EncryptedKeyExchange spake(
crypto::P224EncryptedKeyExchange::kPeerTypeServer, "testPin");
EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS)).Times(1);
EXPECT_CALL(*this, OnPostData(_))
.WillOnce(
testing::Invoke([this, &spake](const base::DictionaryValue& data) {
std::string pairing_type;
EXPECT_TRUE(data.GetString("pairing", &pairing_type));
EXPECT_EQ("embeddedCode", pairing_type);
std::string crypto_type;
EXPECT_TRUE(data.GetString("crypto", &crypto_type));
EXPECT_EQ("p224_spake2", crypto_type);
std::string device_commitment;
base::Base64Encode(spake.GetNextMessage(), &device_commitment);
fetcher_factory_.SetFakeResponse(
GURL("http://host/privet/v3/pairing/start"),
base::StringPrintf(
"{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
device_commitment.c_str()),
net::HTTP_OK, net::URLRequestStatus::SUCCESS);
}));
session_.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE,
base::Bind(&PrivetV3SessionTest::OnPairingStarted,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(session_.fingerprint_.empty());
EXPECT_EQ("Privet anonymous", session_.privet_auth_token_);
EXPECT_CALL(*this, OnCodeConfirmed(Result::STATUS_SUCCESS)).Times(1);
InSequence in_sequence;
EXPECT_CALL(*this, OnPostData(_))
.WillOnce(
testing::Invoke([this, &spake](const base::DictionaryValue& data) {
std::string commitment_base64;
EXPECT_TRUE(data.GetString("clientCommitment", &commitment_base64));
std::string commitment;
EXPECT_TRUE(base::Base64Decode(commitment_base64, &commitment));
std::string session_id;
EXPECT_TRUE(data.GetString("sessionId", &session_id));
EXPECT_EQ("testId", session_id);
EXPECT_EQ(spake.ProcessMessage(commitment),
crypto::P224EncryptedKeyExchange::kResultPending);
std::string fingerprint("testFinterprint");
std::string fingerprint_base64;
base::Base64Encode(fingerprint, &fingerprint_base64);
crypto::HMAC hmac(crypto::HMAC::SHA256);
const std::string& key = spake.GetUnverifiedKey();
EXPECT_TRUE(hmac.Init(key));
std::string signature(hmac.DigestLength(), ' ');
EXPECT_TRUE(hmac.Sign(fingerprint, reinterpret_cast<unsigned char*>(
string_as_array(&signature)),
signature.size()));
std::string signature_base64;
base::Base64Encode(signature, &signature_base64);
fetcher_factory_.SetFakeResponse(
GURL("http://host/privet/v3/pairing/confirm"),
base::StringPrintf(
"{\"certFingerprint\":\"%s\",\"certSignature\":\"%s\"}",
fingerprint_base64.c_str(), signature_base64.c_str()),
net::HTTP_OK, net::URLRequestStatus::SUCCESS);
}));
EXPECT_CALL(*this, OnPostData(_))
.WillOnce(
testing::Invoke([this, &spake](const base::DictionaryValue& data) {
std::string access_token_base64;
EXPECT_TRUE(data.GetString("authCode", &access_token_base64));
std::string access_token;
EXPECT_TRUE(base::Base64Decode(access_token_base64, &access_token));
crypto::HMAC hmac(crypto::HMAC::SHA256);
const std::string& key = spake.GetUnverifiedKey();
EXPECT_TRUE(hmac.Init(key));
EXPECT_TRUE(hmac.Verify("testId", access_token));
fetcher_factory_.SetFakeResponse(
GURL("http://host/privet/v3/auth"),
"{\"accessToken\":\"567\",\"tokenType\":\"testType\","
"\"scope\":\"owner\"}",
net::HTTP_OK, net::URLRequestStatus::SUCCESS);
}));
session_.ConfirmCode("testPin",
base::Bind(&PrivetV3SessionTest::OnCodeConfirmed,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(session_.fingerprint_.empty());
EXPECT_EQ("testType 567", session_.privet_auth_token_);
}
TEST_F(PrivetV3SessionTest, Cancel) {
EXPECT_CALL(*this, OnInitialized(Result::STATUS_SUCCESS, _));
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/info"),
kInfoResponse, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
session_.Init(
base::Bind(&PrivetV3SessionTest::OnInitialized, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*this, OnPairingStarted(Result::STATUS_SUCCESS)).Times(1);
EXPECT_CALL(*this, OnPostData(_))
.WillOnce(testing::Invoke([this](const base::DictionaryValue& data) {
std::string device_commitment;
base::Base64Encode("1234", &device_commitment);
fetcher_factory_.SetFakeResponse(
GURL("http://host/privet/v3/pairing/start"),
base::StringPrintf(
"{\"deviceCommitment\":\"%s\",\"sessionId\":\"testId\"}",
device_commitment.c_str()),
net::HTTP_OK, net::URLRequestStatus::SUCCESS);
}));
session_.StartPairing(PairingType::PAIRING_TYPE_EMBEDDEDCODE,
base::Bind(&PrivetV3SessionTest::OnPairingStarted,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
fetcher_factory_.SetFakeResponse(GURL("http://host/privet/v3/pairing/cancel"),
kInfoResponse, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
EXPECT_CALL(*this, OnPostData(_))
.WillOnce(testing::Invoke([this](const base::DictionaryValue& data) {
std::string session_id;
EXPECT_TRUE(data.GetString("sessionId", &session_id));
}));
}
// TODO(vitalybuka): replace PrivetHTTPClient with regular URL fetcher and
// implement SendMessage test.
} // namespace local_discovery