summaryrefslogtreecommitdiffstats
path: root/remoting/host
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-04 23:04:05 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-04 23:04:05 +0000
commitcdf8c57cbf1d997346e650fb5d0b301f7abc4dd4 (patch)
tree81a7bfd30b29dfaea9cd2644daf06f39d11b17a0 /remoting/host
parentc0035fff490b71ff952b97b6a3d22b1041689979 (diff)
downloadchromium_src-cdf8c57cbf1d997346e650fb5d0b301f7abc4dd4.zip
chromium_src-cdf8c57cbf1d997346e650fb5d0b301f7abc4dd4.tar.gz
chromium_src-cdf8c57cbf1d997346e650fb5d0b301f7abc4dd4.tar.bz2
Added HostKeyPair class, signatures for heartbeat messages.
BUG=None TEST=unittests Review URL: http://codereview.chromium.org/3087003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54991 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/host')
-rw-r--r--remoting/host/chromoting_host.cc15
-rw-r--r--remoting/host/heartbeat_sender.cc107
-rw-r--r--remoting/host/heartbeat_sender.h59
-rw-r--r--remoting/host/heartbeat_sender_unittest.cc148
-rw-r--r--remoting/host/host_key_pair.cc92
-rw-r--r--remoting/host/host_key_pair.h46
-rw-r--r--remoting/host/host_key_pair_unittest.cc71
-rw-r--r--remoting/host/json_host_config.cc1
-rw-r--r--remoting/host/json_host_config_unittest.cc2
-rw-r--r--remoting/host/test_key_pair.h30
10 files changed, 535 insertions, 36 deletions
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc
index 7795de1..063ee83 100644
--- a/remoting/host/chromoting_host.cc
+++ b/remoting/host/chromoting_host.cc
@@ -139,12 +139,13 @@ void ChromotingHost::OnStateChange(JingleClient* jingle_client,
LOG(INFO) << "Host connected as "
<< jingle_client->GetFullJid() << "." << std::endl;
- // Start heartbeating after we connected
- heartbeat_sender_ = new HeartbeatSender();
- heartbeat_sender_->Start(config_, jingle_client_.get());
+ // Start heartbeating after we've connected.
+ heartbeat_sender_->Start();
} else if (state == JingleClient::CLOSED) {
LOG(INFO) << "Host disconnected from talk network." << std::endl;
- heartbeat_sender_ = NULL;
+
+ // Stop heartbeating.
+ heartbeat_sender_->Stop();
// TODO(sergeyu): We should try reconnecting here instead of terminating
// the host.
@@ -219,6 +220,12 @@ void ChromotingHost::DoStart(Task* shutdown_task) {
jingle_client_ = new JingleClient(context_->jingle_thread());
jingle_client_->Init(xmpp_login, xmpp_auth_token,
kChromotingTokenServiceName, this);
+
+ heartbeat_sender_ = new HeartbeatSender();
+ if (!heartbeat_sender_->Init(config_, jingle_client_.get())) {
+ LOG(ERROR) << "Failed to initialize HeartbeatSender.";
+ return;
+ }
}
void ChromotingHost::DoShutdown() {
diff --git a/remoting/host/heartbeat_sender.cc b/remoting/host/heartbeat_sender.cc
index fa234e3..b62b268a 100644
--- a/remoting/host/heartbeat_sender.cc
+++ b/remoting/host/heartbeat_sender.cc
@@ -6,70 +6,103 @@
#include "base/logging.h"
#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "base/time.h"
#include "remoting/base/constants.h"
#include "remoting/host/host_config.h"
#include "remoting/jingle_glue/iq_request.h"
#include "remoting/jingle_glue/jingle_client.h"
#include "remoting/jingle_glue/jingle_thread.h"
-#include "talk/xmpp/constants.h"
-#include "talk/xmllite/xmlelement.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+#include "third_party/libjingle/source/talk/xmpp/constants.h"
namespace remoting {
namespace {
-const char * const kChromotingNamespace = "google:remoting";
-const buzz::QName kHeartbeatQuery(true, kChromotingNamespace, "heartbeat");
-const buzz::QName kHostIdAttr(true, kChromotingNamespace, "hostid");
+const char kHeartbeatQueryTag[] = "heartbeat";
+const char kHostIdAttr[] = "hostid";
+const char kHeartbeatSignatureTag[] = "signature";
+const char kSignatureTimeAttr[] = "time";
// TODO(sergeyu): Make this configurable by the cloud.
const int64 kHeartbeatPeriodMs = 5 * 60 * 1000; // 5 minutes.
}
HeartbeatSender::HeartbeatSender()
- : started_(false) {
+ : state_(CREATED) {
}
-void HeartbeatSender::Start(MutableHostConfig* config, JingleClient* jingle_client) {
- DCHECK(jingle_client);
- DCHECK(!started_);
+HeartbeatSender::~HeartbeatSender() {
+ DCHECK(state_ == CREATED || state_ == INITIALIZED || state_ == STOPPED);
+}
- started_ = true;
+bool HeartbeatSender::Init(MutableHostConfig* config,
+ JingleClient* jingle_client) {
+ DCHECK(jingle_client);
+ DCHECK(state_ == CREATED);
jingle_client_ = jingle_client;
config_ = config;
if (!config_->GetString(kHostIdConfigPath, &host_id_)) {
LOG(ERROR) << "host_id is not defined in the config.";
- return;
+ return false;
}
- jingle_client_->message_loop()->PostTask(
- FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoStart));
+ if (!key_pair_.Load(config)) {
+ return false;
+ }
+
+ state_ = INITIALIZED;
+
+ return true;
}
-void HeartbeatSender::DoStart() {
- DCHECK(MessageLoop::current() == jingle_client_->message_loop());
+void HeartbeatSender::Start() {
+ if (MessageLoop::current() != jingle_client_->message_loop()) {
+ jingle_client_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::Start));
+ return;
+ }
+
+ DCHECK(state_ == INITIALIZED);
+ state_ = STARTED;
- request_.reset(new IqRequest(jingle_client_));
+ request_.reset(jingle_client_->CreateIqRequest());
request_->set_callback(NewCallback(this, &HeartbeatSender::ProcessResponse));
jingle_client_->message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza));
}
+void HeartbeatSender::Stop() {
+ if (MessageLoop::current() != jingle_client_->message_loop()) {
+ jingle_client_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::Stop));
+ return;
+ }
+
+ DCHECK(state_ == STARTED);
+ state_ = STOPPED;
+ request_.reset(NULL);
+}
+
void HeartbeatSender::DoSendStanza() {
- DCHECK(MessageLoop::current() == jingle_client_->message_loop());
+ if (state_ == STARTED) {
+ // |jingle_client_| may be already destroyed if |state_| is set to
+ // |STOPPED|, so don't touch it here unless we are in |STARTED| state.
+ DCHECK(MessageLoop::current() == jingle_client_->message_loop());
- LOG(INFO) << "Sending heartbeat stanza to " << kChromotingBotJid;
+ LOG(INFO) << "Sending heartbeat stanza to " << kChromotingBotJid;
- buzz::XmlElement* stanza = new buzz::XmlElement(kHeartbeatQuery);
- stanza->AddAttr(kHostIdAttr, host_id_);
- request_->SendIq(buzz::STR_SET, kChromotingBotJid, stanza);
+ request_->SendIq(buzz::STR_SET, kChromotingBotJid,
+ CreateHeartbeatMessage());
- // Schedule next heartbeat.
- jingle_client_->message_loop()->PostDelayedTask(
- FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza),
- kHeartbeatPeriodMs);
+ // Schedule next heartbeat.
+ jingle_client_->message_loop()->PostDelayedTask(
+ FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza),
+ kHeartbeatPeriodMs);
+ }
}
void HeartbeatSender::ProcessResponse(const buzz::XmlElement* response) {
@@ -79,4 +112,28 @@ void HeartbeatSender::ProcessResponse(const buzz::XmlElement* response) {
}
}
+buzz::XmlElement* HeartbeatSender::CreateHeartbeatMessage() {
+ buzz::XmlElement* query = new buzz::XmlElement(
+ buzz::QName(kChromotingXmlNamespace, kHeartbeatQueryTag));
+ query->AddAttr(buzz::QName(kChromotingXmlNamespace, kHostIdAttr), host_id_);
+ query->AddElement(CreateSignature());
+ return query;
+}
+
+buzz::XmlElement* HeartbeatSender::CreateSignature() {
+ buzz::XmlElement* signature_tag = new buzz::XmlElement(
+ buzz::QName(kChromotingXmlNamespace, kHeartbeatSignatureTag));
+
+ int64 time = static_cast<int64>(base::Time::Now().ToDoubleT());
+ std::string time_str(base::Int64ToString(time));
+ signature_tag->AddAttr(
+ buzz::QName(kChromotingXmlNamespace, kSignatureTimeAttr), time_str);
+
+ std::string message = jingle_client_->GetFullJid() + ' ' + time_str;
+ std::string signature(key_pair_.GetSignature(message));
+ signature_tag->AddText(signature);
+
+ return signature_tag;
+}
+
} // namespace remoting
diff --git a/remoting/host/heartbeat_sender.h b/remoting/host/heartbeat_sender.h
index 08665c6..07e2568e 100644
--- a/remoting/host/heartbeat_sender.h
+++ b/remoting/host/heartbeat_sender.h
@@ -9,34 +9,81 @@
#include "base/scoped_ptr.h"
#include "base/ref_counted.h"
+#include "remoting/host/host_key_pair.h"
#include "remoting/jingle_glue/iq_request.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
namespace remoting {
class IqRequest;
+class HostKeyPair;
class JingleClient;
class MutableHostConfig;
-// HeartbeatSender periodically sends hertbeats to the chromoting bot.
-// TODO(sergeyu): Write unittest for this class.
+// HeartbeatSender periodically sends heartbeat stanzas to the Chromoting Bot.
+// Each heartbeat stanza looks as follows:
+//
+// <iq type="set" to="remoting@bot.talk.google.com"
+// from="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client">
+// <rem:heartbeat rem:hostid="a1ddb11e-8aef-11df-bccf-18a905b9cb5a"
+// xmlns:rem="google:remoting">
+// <rem:signature rem:time="1279061748">.signature.</rem:signature>
+// </rem:heartbeat>
+// </iq>
+//
+// The time attribute of the signature is the decimal time when the message
+// was sent in second since the epoch (01/01/1970). The signature is a BASE64
+// encoded SHA-1/RSA signature created with the host's private key. The message
+// being signed is the full Jid concatenated with the time value, separated by
+// space. For example, for the heartbeat stanza above the message that is being
+// signed is "user@gmail.com/chromoting123123 1279061748".
+// TODO(sergeyu): Is it enough to sign JID and nothing else?
class HeartbeatSender : public base::RefCountedThreadSafe<HeartbeatSender> {
public:
HeartbeatSender();
+ virtual ~HeartbeatSender();
- // Starts heart-beating for |jingle_client|.
- void Start(MutableHostConfig* config, JingleClient* jingle_client);
+ // Initializes heart-beating for |jingle_client| with the specified
+ // config. Returns false if the config is invalid (e.g. private key
+ // cannot be parsed).
+ bool Init(MutableHostConfig* config, JingleClient* jingle_client);
+
+ // Starts heart-beating. Must be called after init.
+ void Start();
+
+ // Stops heart-beating. Must be called before corresponding JingleClient
+ // is destroyed. This object will not be deleted until Stop() is called,
+ // and it may (and will) crash after JingleClient is destroyed. Heartbeating
+ // cannot be restarted after it has been stopped, A new sender must be created
+ // instead.
+ void Stop();
private:
- void DoStart();
+ FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, DoSendStanza);
+ FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, CreateHeartbeatMessage);
+
+ enum State {
+ CREATED,
+ INITIALIZED,
+ STARTED,
+ STOPPED,
+ };
+
void DoSendStanza();
+ // Helper methods used by DoSendStanza() to generate heartbeat stanzas.
+ // Caller owns the result.
+ buzz::XmlElement* CreateHeartbeatMessage();
+ buzz::XmlElement* CreateSignature();
+
void ProcessResponse(const buzz::XmlElement* response);
- bool started_;
+ State state_;
scoped_refptr<MutableHostConfig> config_;
JingleClient* jingle_client_;
scoped_ptr<IqRequest> request_;
std::string host_id_;
+ HostKeyPair key_pair_;
};
} // namespace remoting
diff --git a/remoting/host/heartbeat_sender_unittest.cc b/remoting/host/heartbeat_sender_unittest.cc
new file mode 100644
index 0000000..6ac2037
--- /dev/null
+++ b/remoting/host/heartbeat_sender_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2010 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 "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/ref_counted.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_number_conversions.h"
+#include "media/base/data_buffer.h"
+#include "remoting/base/constants.h"
+#include "remoting/host/heartbeat_sender.h"
+#include "remoting/host/host_key_pair.h"
+#include "remoting/host/json_host_config.h"
+#include "remoting/host/test_key_pair.h"
+#include "remoting/jingle_glue/iq_request.h"
+#include "remoting/jingle_glue/jingle_client.h"
+#include "remoting/jingle_glue/jingle_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+#include "third_party/libjingle/source/talk/xmpp/constants.h"
+
+using testing::_;
+using testing::DeleteArg;
+using testing::DoAll;
+using testing::NotNull;
+using testing::Return;
+
+namespace remoting {
+
+namespace {
+const char kHostId[] = "0";
+const char kTestJid[] = "user@gmail.com/chromoting123";
+const int64 kTestTime = 123123123;
+} // namespace
+
+class MockJingleClient : public JingleClient {
+ public:
+ explicit MockJingleClient(JingleThread* thread) : JingleClient(thread) { }
+ MOCK_METHOD0(CreateIqRequest, IqRequest*());
+};
+
+class MockIqRequest : public IqRequest {
+ public:
+ explicit MockIqRequest(JingleClient* jingle_client)
+ : IqRequest(jingle_client) {
+ }
+ MOCK_METHOD3(SendIq, void(const std::string& type,
+ const std::string& addressee,
+ buzz::XmlElement* iq_body));
+};
+
+class HeartbeatSenderTest : public testing::Test {
+ protected:
+ class TestConfigUpdater :
+ public base::RefCountedThreadSafe<TestConfigUpdater> {
+ public:
+ void DoUpdate(scoped_refptr<JsonHostConfig> target) {
+ target->SetString(kHostIdConfigPath, kHostId);
+ target->SetString(kPrivateKeyConfigPath, kTestHostKeyPair);
+ }
+ };
+
+ virtual void SetUp() {
+ ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+ FilePath config_path = test_dir_.path().AppendASCII("test_config.json");
+ config_ = new JsonHostConfig(
+ config_path, base::MessageLoopProxy::CreateForCurrentThread());
+ scoped_refptr<TestConfigUpdater> config_updater(new TestConfigUpdater());
+ config_->Update(
+ NewRunnableMethod(config_updater.get(), &TestConfigUpdater::DoUpdate,
+ config_));
+
+ jingle_thread_.message_loop_ = &message_loop_;
+
+ jingle_client_ = new MockJingleClient(&jingle_thread_);
+ jingle_client_->full_jid_ = kTestJid;
+ }
+
+ JingleThread jingle_thread_;
+ scoped_refptr<MockJingleClient> jingle_client_;
+ MessageLoop message_loop_;
+ ScopedTempDir test_dir_;
+ scoped_refptr<JsonHostConfig> config_;
+};
+
+TEST_F(HeartbeatSenderTest, DoSendStanza) {
+ // This test calls Start() followed by Stop(), and makes sure an Iq
+ // stanza is being send.
+
+ // |iq_request| is freed by HeartbeatSender.
+ MockIqRequest* iq_request = new MockIqRequest(jingle_client_);
+
+ scoped_refptr<HeartbeatSender> heartbeat_sender = new HeartbeatSender();
+ ASSERT_TRUE(heartbeat_sender->Init(config_, jingle_client_));
+
+ EXPECT_CALL(*jingle_client_, CreateIqRequest())
+ .WillOnce(Return(iq_request));
+
+ EXPECT_CALL(*iq_request, SendIq(buzz::STR_SET, kChromotingBotJid, NotNull()))
+ .WillOnce(DoAll(DeleteArg<2>(), Return()));
+
+ heartbeat_sender->Start();
+ message_loop_.RunAllPending();
+
+ heartbeat_sender->Stop();
+ message_loop_.RunAllPending();
+}
+
+TEST_F(HeartbeatSenderTest, CreateHeartbeatMessage) {
+ // This test validates format of the heartbeat stanza.
+
+ scoped_refptr<HeartbeatSender> heartbeat_sender = new HeartbeatSender();
+ ASSERT_TRUE(heartbeat_sender->Init(config_, jingle_client_));
+
+ int64 start_time = static_cast<int64>(base::Time::Now().ToDoubleT());
+
+ scoped_ptr<buzz::XmlElement> stanza(
+ heartbeat_sender->CreateHeartbeatMessage());
+ ASSERT_TRUE(stanza.get() != NULL);
+
+ EXPECT_TRUE(buzz::QName(kChromotingXmlNamespace, "heartbeat") ==
+ stanza->Name());
+ EXPECT_EQ(std::string(kHostId),
+ stanza->Attr(buzz::QName(kChromotingXmlNamespace, "hostid")));
+
+ buzz::QName signature_tag(kChromotingXmlNamespace, "signature");
+ buzz::XmlElement* signature = stanza->FirstNamed(signature_tag);
+ ASSERT_TRUE(signature != NULL);
+ EXPECT_TRUE(stanza->NextNamed(signature_tag) == NULL);
+
+ std::string time_str =
+ signature->Attr(buzz::QName(kChromotingXmlNamespace, "time"));
+ int64 time;
+ EXPECT_TRUE(base::StringToInt64(time_str, &time));
+ int64 now = static_cast<int64>(base::Time::Now().ToDoubleT());
+ EXPECT_LE(start_time, time);
+ EXPECT_GE(now, time);
+
+ HostKeyPair key_pair;
+ key_pair.LoadFromString(kTestHostKeyPair);
+ std::string expected_signature =
+ key_pair.GetSignature(std::string(kTestJid) + ' ' + time_str);
+ EXPECT_EQ(expected_signature, signature->BodyText());
+}
+
+} // namespace remoting
diff --git a/remoting/host/host_key_pair.cc b/remoting/host/host_key_pair.cc
new file mode 100644
index 0000000..f2ec6987
--- /dev/null
+++ b/remoting/host/host_key_pair.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2010 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 "remoting/host/host_key_pair.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/crypto/rsa_private_key.h"
+#include "base/crypto/signature_creator.h"
+#include "base/logging.h"
+#include "base/task.h"
+#include "remoting/host/host_config.h"
+
+namespace remoting {
+
+HostKeyPair::HostKeyPair() { }
+
+HostKeyPair::~HostKeyPair() { }
+
+void HostKeyPair::Generate() {
+ key_.reset(base::RSAPrivateKey::Create(2048));
+}
+
+bool HostKeyPair::LoadFromString(const std::string& key_base64) {
+ std::string key_str;
+ if (!base::Base64Decode(key_base64, &key_str)) {
+ LOG(ERROR) << "Failed to decode private key.";
+ return false;
+ }
+
+ std::vector<uint8> key_buf(key_str.begin(), key_str.end());
+ key_.reset(base::RSAPrivateKey::CreateFromPrivateKeyInfo(key_buf));
+ if (key_.get() == NULL) {
+ LOG(ERROR) << "Invalid private key.";
+ return false;
+ }
+
+ return true;
+}
+
+bool HostKeyPair::Load(HostConfig* host_config) {
+ std::string key_base64;
+ if (!host_config->GetString(kPrivateKeyConfigPath, &key_base64)) {
+ LOG(ERROR) << "Private key wasn't found in the config file.";
+ return false;
+ }
+ return LoadFromString(key_base64);
+}
+
+void HostKeyPair::Save(MutableHostConfig* host_config) {
+ // Check that the key initialized.
+ DCHECK(key_.get() != NULL);
+
+ host_config->Update(
+ NewRunnableMethod(this, &HostKeyPair::DoSave, host_config));
+}
+
+void HostKeyPair::DoSave(MutableHostConfig* host_config) const {
+ std::vector<uint8> key_buf;
+ key_->ExportPrivateKey(&key_buf);
+ std::string key_str(key_buf.begin(), key_buf.end());
+ std::string key_base64;
+ base::Base64Encode(key_str, &key_base64);
+ host_config->SetString(kPrivateKeyConfigPath, key_base64);
+}
+
+std::string HostKeyPair::GetPublicKey() const {
+ std::vector<uint8> public_key;
+ key_->ExportPublicKey(&public_key);
+ std::string public_key_str(public_key.begin(), public_key.end());
+ std::string public_key_base64;
+ base::Base64Encode(public_key_str, &public_key_base64);
+ return public_key_base64;
+}
+
+std::string HostKeyPair::GetSignature(const std::string& message) const {
+ scoped_ptr<base::SignatureCreator> signature_creator(
+ base::SignatureCreator::Create(key_.get()));
+ signature_creator->Update(reinterpret_cast<const uint8*>(message.c_str()),
+ message.length());
+ std::vector<uint8> signature_buf;
+ signature_creator->Final(&signature_buf);
+ std::string signature_str(signature_buf.begin(), signature_buf.end());
+ std::string signature_base64;
+ base::Base64Encode(signature_str, &signature_base64);
+ return signature_base64;
+}
+
+} // namespace remoting
diff --git a/remoting/host/host_key_pair.h b/remoting/host/host_key_pair.h
new file mode 100644
index 0000000..92ee4aa
--- /dev/null
+++ b/remoting/host/host_key_pair.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2010 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.
+
+#ifndef REMOTING_HOST_HOST_KEY_PAIR_H_
+#define REMOTING_HOST_HOST_KEY_PAIR_H_
+
+#include <string>
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+
+namespace base {
+class RSAPrivateKey;
+} // namespace base
+
+namespace remoting {
+
+class HostConfig;
+class MutableHostConfig;
+
+class HostKeyPair {
+ public:
+ HostKeyPair();
+ ~HostKeyPair();
+
+ void Generate();
+ bool LoadFromString(const std::string& key_base64);
+ bool Load(HostConfig* host_config);
+ void Save(MutableHostConfig* host_config);
+
+ std::string GetPublicKey() const;
+ std::string GetSignature(const std::string& message) const;
+
+ private:
+ void DoSave(MutableHostConfig* host_config) const;
+
+ scoped_ptr<base::RSAPrivateKey> key_;
+};
+
+} // namespace remoting
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::HostKeyPair);
+
+#endif // REMOTING_HOST_HOST_KEY_PAIR_H_
diff --git a/remoting/host/host_key_pair_unittest.cc b/remoting/host/host_key_pair_unittest.cc
new file mode 100644
index 0000000..e5af756
--- /dev/null
+++ b/remoting/host/host_key_pair_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2010 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 <string>
+
+#include "base/base64.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/ref_counted.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "remoting/host/host_key_pair.h"
+#include "remoting/host/json_host_config.h"
+#include "remoting/host/test_key_pair.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+const char kTestMessage[] = "Test Message";
+
+// |kTestMessage| signed with the key from |kTestHostKeyPair|.
+const char kExpectedSignature[] =
+"LfUyXU2AiKM4rpWivUR3bLiQiRt1W3iIenNfJEB8RWyoEfnvSBoD52x8q9yFvtLFDEMPWyIrwM+N2"
+"LuaWBKG1c0R7h+twBgvpExzZneJl+lbGMRx9ba8m/KAFrUWA/NRzOen2NHCuPybOEasgrPgGWBrmf"
+"gDcvyW8QiGuKLopGj/4c5CQT4yE8JjsyU3Qqo2ZPK4neJYQhOmAlg+Q5dAPLpzWMj5HQyOVHJaSXZ"
+"Y8vl/LiKvbdofYLeYNVKAE4q5mfpQMrsysPYpbxBV60AhFyrvtC040MFGcflKQRZNiZwMXVb7DclC"
+"BPgvK7rI5Y0ERtVm+yNmH7vCivfyAnDUYA==";
+}
+
+
+class HostKeyPairTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+ FilePath config_path = test_dir_.path().AppendASCII("test_config.json");
+ config_ = new JsonHostConfig(
+ config_path, base::MessageLoopProxy::CreateForCurrentThread());
+ }
+
+ MessageLoop message_loop_;
+ ScopedTempDir test_dir_;
+ scoped_refptr<JsonHostConfig> config_;
+};
+
+TEST_F(HostKeyPairTest, SaveLoad) {
+ // Save a key to a config, then load it back, and verify that we
+ // generate the same signature with both keys.
+ HostKeyPair exported_key;
+ exported_key.LoadFromString(kTestHostKeyPair);
+ exported_key.Save(config_);
+
+ HostKeyPair imported_key;
+ imported_key.Load(config_);
+
+ ASSERT_EQ(exported_key.GetSignature(kTestMessage),
+ imported_key.GetSignature(kTestMessage));
+}
+
+TEST_F(HostKeyPairTest, Signatures) {
+ // Sign a message and check that we get expected signature.
+ HostKeyPair key_pair;
+ key_pair.LoadFromString(kTestHostKeyPair);
+
+ std::string signature_base64 = key_pair.GetSignature(kTestMessage);
+ ASSERT_EQ(signature_base64, std::string(kExpectedSignature));
+}
+
+} // namespace remoting
diff --git a/remoting/host/json_host_config.cc b/remoting/host/json_host_config.cc
index cd868ce..897402c 100644
--- a/remoting/host/json_host_config.cc
+++ b/remoting/host/json_host_config.cc
@@ -17,6 +17,7 @@ JsonHostConfig::JsonHostConfig(
const FilePath& filename,
base::MessageLoopProxy* file_message_loop_proxy)
: filename_(filename),
+ values_(new DictionaryValue()),
message_loop_proxy_(file_message_loop_proxy) {
}
diff --git a/remoting/host/json_host_config_unittest.cc b/remoting/host/json_host_config_unittest.cc
index 534b4c5..bd1bc14 100644
--- a/remoting/host/json_host_config_unittest.cc
+++ b/remoting/host/json_host_config_unittest.cc
@@ -14,7 +14,7 @@
namespace remoting {
namespace {
-const char *kTestConfig =
+const char* kTestConfig =
"{\n"
" \"xmpp_login\" : \"test@gmail.com\",\n"
" \"xmpp_auth_token\" : \"TEST_AUTH_TOKEN\",\n"
diff --git a/remoting/host/test_key_pair.h b/remoting/host/test_key_pair.h
new file mode 100644
index 0000000..cb46e4c
--- /dev/null
+++ b/remoting/host/test_key_pair.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 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.
+
+namespace remoting {
+// An RSA keypair used in unittests.
+const char kTestHostKeyPair[] =
+"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDeeT/wgtzQkGoeA5LRQI4iXibQZ"
+"R3YUiJn/oVz0P+KLJSCWL4Uhjl3CytKg0nzuxgaJ9UOHxrl91k1XnLnwJytqkjl8kgWle+JJbiDx3"
+"+3WBxmSjPWmmaPNuBebpyTD1N3TJEPRwe7vfYiOz56jzBPSm3gqIeRvJGIC+Ou+Q0OV+ISTu2Jjtj"
+"MP3NOLfXjoZfoovPkZAB3/cjwjlBbd+0XWz+b3cAe5zfzcZkDvw6L4htwl/+MlRk7XX1U2dEoaI+x"
+"/kZkigfJdfw7k5RCTmygp1A94rUXBaR5m7p+5eVChTXYjqmh0PrEwVr88D/F58zByma5ZNGFyyj1u"
+"nXTNUUZAgMBAAECggEAKFvUppwG1Ost5Q3+52kPn9p2rh154ZFc3oLE0PLcOMPHmTHiIbUL3bWv/8"
+"97bfTF9ZC+TNaFuaw6ibz9mV9OzQ50NQrT2w0OLFIke/uIBsm8NS5G4yqVamupHlhwnRkNqjnAowX"
+"euEdIzIL03aT2PZwh/Lx2A5Ey0XxerJJPtQcTp1UTSdRafAN+gSvS25DbLUUoAhG4J3auaeD3rg/2"
+"REeCeqaRrHbD33kB8Sw/u/dZ6sKE2IKeFIzNZBzihYa62Q1YWcFfYpPe9b+9CFM85kKgRUZMJtxQl"
+"aSDpi5eK5+EvyQdCQY/eRvhZLOuqD5EBiNZiYUfcWDk582WkFUX9QKBgQD85odRTpZRW6T5adok7I"
+"TexReoUGxqSIyPQfAbdTVXP+yRr0a8WLZSL3wYh7i4qR5B7Rg2K7XZxj/RWzI5cTicvJ7MaD7/N1n"
+"X25bPQwgZKXSVJ0vfde4MHzu7w8cltjWz7crO/7qy6kyOi13jI43fixMhoALJHZJIN7A8m10MxwKB"
+"gQDhM0HhSYNSI5UEavELyKEyXTj4b6XNJ2SGFsM1gHDJeKarSzPkxEPhxt5S8Hfr5e2wmXl27gd6u"
+"52pizriA/H98dvneeXvcYETLbysBKFnWdaJ6P4d45MTEVWnNTWFpbnDMH2GaqJ9WpwxOYrh4QM1nm"
+"4Q/kfkpwDzrQpHu5Z/HwKBgQCbpwcB8+xwhoczOwMYVrowof29ikIv6Bca1OqC+9Cosp9XyxkuMyu"
+"DydYHAwGeJfiJevO744s5TdtZb1eqIxVE4pKaHE8ppVeWk4BucEM7uVgXtOft0ReCPnb1Sbn4a/0B"
+"kQmR/bNbCvmzgfN3KRrYyzArMeCFB75Q1HWT18udYQKBgQCFX7Qe10j9NrGk4ilMj1BY4blB+e26a"
+"SAyAf0vDJi/2cLsJCfhzxNc/kjh2iVMAusY8mxrsWGgoMkphpojDa8edUWF/D3f59tIHohlYICvEx"
+"fqqaEG1qdeXZ/a3bqLIrG4FlnhZ/pAP4/N34SPbpJWCTSqv86YbwgzSUKTZVgmhQKBgAZbvdZM8x8"
+"Et5ho7YqrrBWTwnvMLw4G7qftiMco15JcMAXhfGhmlDI8OUq7MlXVCUWwdZtRYcAyWJcqhWQ3yrXv"
+"8cWi9hxwNGPuof5PchDDuR+cwe8gZvpzwfFDFTgJHIBe64CkFd9Oeb5CvEffbrP04mpWvjVeMcjjz"
+"ZA6a+IB";
+} // namespace remoting