summaryrefslogtreecommitdiffstats
path: root/components/gcm_driver
diff options
context:
space:
mode:
authorpeter <peter@chromium.org>2015-10-09 03:18:52 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-09 10:19:37 +0000
commitadd31f6fb477348d2b6a755cc8b6107a7fcb5339 (patch)
tree836e2bbe1c1591e260cfc3a35158acbb14d3c939 /components/gcm_driver
parentdaae8f2139bd5681ff0bfbf620a86b645934cd34 (diff)
downloadchromium_src-add31f6fb477348d2b6a755cc8b6107a7fcb5339.zip
chromium_src-add31f6fb477348d2b6a755cc8b6107a7fcb5339.tar.gz
chromium_src-add31f6fb477348d2b6a755cc8b6107a7fcb5339.tar.bz2
Teach the GCM Driver how to decrypt incoming messages.
This CL teaches the GCM Driver how it can decrypt incoming messages according to draft-thomson-webpush-encryption-01 and draft-thomson-http-encryption-01. Only the Web Push protocol is supported, decryption for messages sent through the regular GCM protocol will be supported later. While this makes end-to-end encryption work, we do *not* enable this functionality by default yet, pending resolution of some ongoing discussions with the IETF and W3C Push working groups. BUG=486040 Review URL: https://codereview.chromium.org/1243563002 Cr-Commit-Position: refs/heads/master@{#353256}
Diffstat (limited to 'components/gcm_driver')
-rw-r--r--components/gcm_driver/common/gcm_messages.cc2
-rw-r--r--components/gcm_driver/common/gcm_messages.h4
-rw-r--r--components/gcm_driver/crypto/BUILD.gn1
-rw-r--r--components/gcm_driver/crypto/gcm_encryption_provider.cc135
-rw-r--r--components/gcm_driver/crypto/gcm_encryption_provider.h47
-rw-r--r--components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc319
-rw-r--r--components/gcm_driver/crypto/gcm_message_cryptographer.cc5
-rw-r--r--components/gcm_driver/crypto/gcm_message_cryptographer.h4
-rw-r--r--components/gcm_driver/gcm_driver.cc23
-rw-r--r--components/gcm_driver/gcm_driver.h7
-rw-r--r--components/gcm_driver/gcm_driver_android.cc2
-rw-r--r--components/gcm_driver/gcm_driver_desktop.cc2
12 files changed, 544 insertions, 7 deletions
diff --git a/components/gcm_driver/common/gcm_messages.cc b/components/gcm_driver/common/gcm_messages.cc
index 0d44fce..1c092a3 100644
--- a/components/gcm_driver/common/gcm_messages.cc
+++ b/components/gcm_driver/common/gcm_messages.cc
@@ -15,7 +15,7 @@ OutgoingMessage::OutgoingMessage() : time_to_live(kMaximumTTL) {
OutgoingMessage::~OutgoingMessage() {
}
-IncomingMessage::IncomingMessage() {
+IncomingMessage::IncomingMessage() : decrypted(false) {
}
IncomingMessage::~IncomingMessage() {
diff --git a/components/gcm_driver/common/gcm_messages.h b/components/gcm_driver/common/gcm_messages.h
index 8d8c5b9..a6a071c 100644
--- a/components/gcm_driver/common/gcm_messages.h
+++ b/components/gcm_driver/common/gcm_messages.h
@@ -38,6 +38,10 @@ struct GCM_DRIVER_EXPORT IncomingMessage {
std::string collapse_key;
std::string sender_id;
std::string raw_data;
+
+ // Whether the contents of the message have been decrypted, and are
+ // available in |raw_data|.
+ bool decrypted;
};
} // namespace gcm
diff --git a/components/gcm_driver/crypto/BUILD.gn b/components/gcm_driver/crypto/BUILD.gn
index 8e0c47e..26fe643 100644
--- a/components/gcm_driver/crypto/BUILD.gn
+++ b/components/gcm_driver/crypto/BUILD.gn
@@ -39,6 +39,7 @@ source_set("unit_tests") {
testonly = true
sources = [
"encryption_header_parsers_unittest.cc",
+ "gcm_encryption_provider_unittest.cc",
"gcm_key_store_unittest.cc",
"gcm_message_cryptographer_unittest.cc",
]
diff --git a/components/gcm_driver/crypto/gcm_encryption_provider.cc b/components/gcm_driver/crypto/gcm_encryption_provider.cc
index 2b775e4..5745879 100644
--- a/components/gcm_driver/crypto/gcm_encryption_provider.cc
+++ b/components/gcm_driver/crypto/gcm_encryption_provider.cc
@@ -4,17 +4,31 @@
#include "components/gcm_driver/crypto/gcm_encryption_provider.h"
+#include <vector>
+
+#include "base/base64.h"
#include "base/bind.h"
#include "base/logging.h"
+#include "components/gcm_driver/common/gcm_messages.h"
+#include "components/gcm_driver/crypto/encryption_header_parsers.h"
#include "components/gcm_driver/crypto/gcm_key_store.h"
+#include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
#include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h"
+#include "crypto/curve25519.h"
namespace gcm {
+namespace {
+
+const char kEncryptionProperty[] = "encryption";
+const char kEncryptionKeyProperty[] = "encryption_key";
+
// Directory in the GCM Store in which the encryption database will be stored.
const base::FilePath::CharType kEncryptionDirectoryName[] =
FILE_PATH_LITERAL("Encryption");
+} // namespace
+
GCMEncryptionProvider::GCMEncryptionProvider()
: weak_ptr_factory_(this) {
}
@@ -46,6 +60,74 @@ void GCMEncryptionProvider::GetPublicKey(const std::string& app_id,
weak_ptr_factory_.GetWeakPtr(), app_id, callback));
}
+bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message)
+ const {
+ // The Web Push protocol requires the encryption and encryption_key properties
+ // to be set, and the raw_data field to be populated with the payload.
+ if (message.data.find(kEncryptionProperty) == message.data.end() ||
+ message.data.find(kEncryptionKeyProperty) == message.data.end())
+ return false;
+
+ // TODO(peter): Support decrypting messages that were sent using the existing
+ // GCM protocol, as opposed to the Web Push protocol.
+
+ return message.raw_data.size() > 0;
+}
+
+void GCMEncryptionProvider::DecryptMessage(
+ const std::string& app_id,
+ const IncomingMessage& message,
+ const MessageDecryptedCallback& success_callback,
+ const DecryptionFailedCallback& failure_callback) {
+ DCHECK(key_store_);
+
+ const auto& encryption_header = message.data.find(kEncryptionProperty);
+ const auto& encryption_key_header = message.data.find(kEncryptionKeyProperty);
+
+ // Callers are expected to call IsEncryptedMessage() prior to this method.
+ DCHECK(encryption_header != message.data.end());
+ DCHECK(encryption_key_header != message.data.end());
+
+ std::vector<EncryptionHeaderValues> encryption_header_values;
+ if (!ParseEncryptionHeader(encryption_header->second,
+ &encryption_header_values)) {
+ DLOG(ERROR) << "Unable to parse the value of the Encryption header";
+ failure_callback.Run(DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER);
+ return;
+ }
+
+ if (encryption_header_values.size() != 1u ||
+ encryption_header_values[0].salt.size() !=
+ GCMMessageCryptographer::kSaltSize) {
+ DLOG(ERROR) << "Invalid values supplied in the Encryption header";
+ failure_callback.Run(DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER);
+ return;
+ }
+
+ std::vector<EncryptionKeyHeaderValues> encryption_key_header_values;
+ if (!ParseEncryptionKeyHeader(encryption_key_header->second,
+ &encryption_key_header_values)) {
+ DLOG(ERROR) << "Unable to parse the value of the Encryption-Key header";
+ failure_callback.Run(DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER);
+ return;
+ }
+
+ if (encryption_key_header_values.size() != 1u ||
+ encryption_key_header_values[0].dh.size() != crypto::curve25519::kBytes) {
+ DLOG(ERROR) << "Invalid values supplied in the Encryption-Key header";
+ failure_callback.Run(DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER);
+ return;
+ }
+
+ key_store_->GetKeys(
+ app_id, base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey,
+ weak_ptr_factory_.GetWeakPtr(), message,
+ success_callback, failure_callback,
+ encryption_header_values[0].salt,
+ encryption_key_header_values[0].dh,
+ encryption_header_values[0].rs));
+}
+
void GCMEncryptionProvider::DidGetPublicKey(const std::string& app_id,
const PublicKeyCallback& callback,
const KeyPair& pair) {
@@ -72,4 +154,57 @@ void GCMEncryptionProvider::DidCreatePublicKey(
callback.Run(pair.public_key());
}
+void GCMEncryptionProvider::DecryptMessageWithKey(
+ const IncomingMessage& message,
+ const MessageDecryptedCallback& success_callback,
+ const DecryptionFailedCallback& failure_callback,
+ const std::string& salt,
+ const std::string& dh,
+ uint64_t rs,
+ const KeyPair& pair) {
+ if (!pair.IsInitialized()) {
+ DLOG(ERROR) << "Unable to retrieve the keys for the incoming message.";
+ failure_callback.Run(DECRYPTION_FAILURE_NO_KEYS);
+ return;
+ }
+
+ DCHECK_EQ(KeyPair::ECDH_CURVE_25519, pair.type());
+
+ // TODO(peter): Support explicit keys for the decryption that don't rely
+ // on use of an HKDF.
+
+ uint8_t shared_key[crypto::curve25519::kBytes];
+
+ // Calculate the shared secret for the message.
+ crypto::curve25519::ScalarMult(
+ reinterpret_cast<const unsigned char*>(pair.private_key().data()),
+ reinterpret_cast<const unsigned char*>(dh.data()),
+ shared_key);
+
+ base::StringPiece shared_key_string_piece(
+ reinterpret_cast<char*>(shared_key), crypto::curve25519::kBytes);
+
+ std::string plaintext;
+
+ GCMMessageCryptographer cryptographer;
+ if (!cryptographer.Decrypt(message.raw_data, shared_key_string_piece, salt,
+ rs, &plaintext)) {
+ DLOG(ERROR) << "Unable to decrypt the incoming data.";
+ failure_callback.Run(DECRYPTION_FAILURE_INVALID_PAYLOAD);
+ return;
+ }
+
+ IncomingMessage decrypted_message;
+ decrypted_message.collapse_key = message.collapse_key;
+ decrypted_message.sender_id = message.sender_id;
+ decrypted_message.raw_data.swap(plaintext);
+ decrypted_message.decrypted = true;
+
+ // There must be no data associated with the decrypted message at this point,
+ // to make sure that we don't end up in an infinite decryption loop.
+ DCHECK_EQ(0u, decrypted_message.data.size());
+
+ success_callback.Run(decrypted_message);
+}
+
} // namespace gcm
diff --git a/components/gcm_driver/crypto/gcm_encryption_provider.h b/components/gcm_driver/crypto/gcm_encryption_provider.h
index a395118..c4ff271 100644
--- a/components/gcm_driver/crypto/gcm_encryption_provider.h
+++ b/components/gcm_driver/crypto/gcm_encryption_provider.h
@@ -9,6 +9,7 @@
#include <string>
#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
@@ -20,6 +21,7 @@ class SequencedTaskRunner;
namespace gcm {
class GCMKeyStore;
+struct IncomingMessage;
class KeyPair;
// Provider that enables the GCM Driver to deal with encryption key management
@@ -29,6 +31,29 @@ class GCMEncryptionProvider {
// Callback to be invoked when the public encryption key is available.
using PublicKeyCallback = base::Callback<void(const std::string&)>;
+ // Callback to be invoked when a message has been decrypted.
+ using MessageDecryptedCallback = base::Callback<void(const IncomingMessage&)>;
+
+ // Reasons why the decryption of an incoming message can fail.
+ enum DecryptionFailure {
+ DECRYPTION_FAILURE_UNKNOWN,
+
+ // The contents of the Encryption HTTP header could not be parsed.
+ DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
+
+ // The contents of the Encryption-Key HTTP header could not be parsed.
+ DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
+
+ // No public/private key-pair was associated with the app_id.
+ DECRYPTION_FAILURE_NO_KEYS,
+
+ // The payload could not be decrypted as AES-128-GCM.
+ DECRYPTION_FAILURE_INVALID_PAYLOAD
+ };
+
+ // Callback to be invoked when a message cannot be decoded.
+ using DecryptionFailedCallback = base::Callback<void(DecryptionFailure)>;
+
GCMEncryptionProvider();
~GCMEncryptionProvider();
@@ -44,7 +69,21 @@ class GCMEncryptionProvider {
void GetPublicKey(const std::string& app_id,
const PublicKeyCallback& callback);
+ // Determines whether |message| contains encrypted content.
+ bool IsEncryptedMessage(const IncomingMessage& message) const;
+
+ // Asynchronously decrypts |message|. The |success_callback| will be invoked
+ // the message could be decrypted successfully, accompanied by the decrypted
+ // payload of the message. When decryption failed, the |failure_callback| will
+ // be invoked with the reason that encryption failed.
+ void DecryptMessage(const std::string& app_id,
+ const IncomingMessage& message,
+ const MessageDecryptedCallback& success_callback,
+ const DecryptionFailedCallback& failure_callback);
+
private:
+ FRIEND_TEST_ALL_PREFIXES(GCMEncryptionProviderTest, EncryptionRoundTrip);
+
void DidGetPublicKey(const std::string& app_id,
const PublicKeyCallback& callback,
const KeyPair& pair);
@@ -52,6 +91,14 @@ class GCMEncryptionProvider {
void DidCreatePublicKey(const PublicKeyCallback& callback,
const KeyPair& pair);
+ void DecryptMessageWithKey(const IncomingMessage& message,
+ const MessageDecryptedCallback& success_callback,
+ const DecryptionFailedCallback& failure_callback,
+ const std::string& salt,
+ const std::string& dh,
+ uint64_t rs,
+ const KeyPair& pair);
+
scoped_ptr<GCMKeyStore> key_store_;
base::WeakPtrFactory<GCMEncryptionProvider> weak_ptr_factory_;
diff --git a/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc b/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc
new file mode 100644
index 0000000..14a6f71
--- /dev/null
+++ b/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc
@@ -0,0 +1,319 @@
+// Copyright 2015 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 "components/gcm_driver/crypto/gcm_encryption_provider.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "components/gcm_driver/common/gcm_messages.h"
+#include "components/gcm_driver/crypto/gcm_key_store.h"
+#include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
+#include "crypto/curve25519.h"
+#include "crypto/random.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+namespace {
+
+const char kExampleAppId[] = "my-app-id";
+const char kExampleMessage[] = "Hello, world, this is the GCM Driver!";
+
+const char kValidEncryptionHeader[] =
+ "keyid=foo;salt=MTIzNDU2Nzg5MDEyMzQ1Ng;rs=1024";
+const char kInvalidEncryptionHeader[] = "keyid";
+
+const char kValidEncryptionKeyHeader[] =
+ "keyid=foo;dh=NjU0MzIxMDk4NzY1NDMyMTEyMzQ1Njc4OTAxMjM0NTY";
+const char kInvalidEncryptionKeyHeader[] = "keyid";
+
+// TODO(peter): Unify the Base64Url implementations. https://crbug.com/536745.
+void Base64UrlEncode(const std::string& decoded_input,
+ std::string* encoded_output) {
+ base::Base64Encode(decoded_input, encoded_output);
+ base::ReplaceChars(*encoded_output, "+", "-", encoded_output);
+ base::ReplaceChars(*encoded_output, "/", "_", encoded_output);
+}
+
+} // namespace
+
+class GCMEncryptionProviderTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+
+ encryption_provider_.reset(new GCMEncryptionProvider);
+ encryption_provider_->Init(scoped_temp_dir_.path(),
+ message_loop_.task_runner());
+ }
+
+ void TearDown() override {
+ encryption_provider_.reset();
+
+ // |encryption_provider_| owns a ProtoDatabaseImpl whose destructor deletes
+ // the underlying LevelDB database on the task runner.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // To be used as a callback for GCMEncryptionProvider::GetPublicKey().
+ void DidGetPublicKey(std::string* key_out, const std::string& key) {
+ *key_out = key;
+ }
+
+ // To be used as a callback for GCMKeyStore::CreateKeys().
+ void DidCreateKeys(KeyPair* pair_out, const KeyPair& pair) {
+ *pair_out = pair;
+ }
+
+ protected:
+ // Tri-state enumaration listing whether the decryption operation is idle
+ // (hasn't started yet), succeeded or failed.
+ enum DecryptionResult {
+ DECRYPTION_IDLE,
+ DECRYPTION_SUCCEEDED,
+ DECRYPTION_FAILED
+ };
+
+ // Decrypts the |message| and then synchronously waits until either the
+ // success or failure callbacks has been invoked.
+ void Decrypt(const IncomingMessage& message) {
+ decryption_result_ = DECRYPTION_IDLE;
+ encryption_provider_->DecryptMessage(
+ kExampleAppId, message,
+ base::Bind(&GCMEncryptionProviderTest::OnDecryptionSucceeded,
+ base::Unretained(this)),
+ base::Bind(&GCMEncryptionProviderTest::OnDecryptionFailed,
+ base::Unretained(this)));
+
+ // The encryption keys will be read asynchronously.
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_NE(decryption_result_, DECRYPTION_IDLE);
+ }
+
+ DecryptionResult decryption_result() { return decryption_result_; }
+
+ const IncomingMessage& decrypted_message() { return decrypted_message_; }
+
+ GCMEncryptionProvider::DecryptionFailure failure_reason() {
+ return failure_reason_;
+ }
+
+ GCMEncryptionProvider* encryption_provider() {
+ return encryption_provider_.get();
+ }
+
+ private:
+ void OnDecryptionSucceeded(const IncomingMessage& message) {
+ decryption_result_ = DECRYPTION_SUCCEEDED;
+ decrypted_message_ = message;
+ }
+
+ void OnDecryptionFailed(GCMEncryptionProvider::DecryptionFailure reason) {
+ decryption_result_ = DECRYPTION_FAILED;
+ failure_reason_ = reason;
+ }
+
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir scoped_temp_dir_;
+
+ scoped_ptr<GCMEncryptionProvider> encryption_provider_;
+
+ DecryptionResult decryption_result_ = DECRYPTION_IDLE;
+ GCMEncryptionProvider::DecryptionFailure failure_reason_ =
+ GCMEncryptionProvider::DECRYPTION_FAILURE_UNKNOWN;
+
+ IncomingMessage decrypted_message_;
+};
+
+TEST_F(GCMEncryptionProviderTest, IsEncryptedMessage) {
+ // Both the Encryption and Encryption-Key headers must be present, and the raw
+ // data must be non-empty for a message to be considered encrypted.
+
+ IncomingMessage empty_message;
+ EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(empty_message));
+
+ IncomingMessage single_header_message;
+ single_header_message.data["encryption"] = "";
+ EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
+ single_header_message));
+
+ IncomingMessage double_header_message;
+ double_header_message.data["encryption"] = "";
+ double_header_message.data["encryption_key"] = "";
+ EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
+ double_header_message));
+
+ IncomingMessage double_header_with_data_message;
+ double_header_with_data_message.data["encryption"] = "";
+ double_header_with_data_message.data["encryption_key"] = "";
+ double_header_with_data_message.raw_data = "foo";
+ EXPECT_TRUE(encryption_provider()->IsEncryptedMessage(
+ double_header_with_data_message));
+}
+
+TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionHeaderParsing) {
+ // The Encryption header must be parsable and contain valid values.
+ // Note that this is more extensively tested in EncryptionHeaderParsersTest.
+
+ IncomingMessage invalid_message;
+ invalid_message.data["encryption"] = kInvalidEncryptionHeader;
+ invalid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
+ failure_reason());
+
+ IncomingMessage valid_message;
+ valid_message.data["encryption"] = kValidEncryptionHeader;
+ valid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
+ failure_reason());
+}
+
+TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionKeyHeaderParsing) {
+ // The Encryption-Key header must be parsable and contain valid values.
+ // Note that this is more extensively tested in EncryptionHeaderParsersTest.
+
+ IncomingMessage invalid_message;
+ invalid_message.data["encryption"] = kValidEncryptionHeader;
+ invalid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_EQ(
+ GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
+ failure_reason());
+
+ IncomingMessage valid_message;
+ valid_message.data["encryption"] = kInvalidEncryptionHeader;
+ valid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_NE(
+ GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
+ failure_reason());
+}
+
+TEST_F(GCMEncryptionProviderTest, VerifiesExistingKeys) {
+ // When both headers are valid, the encryption keys still must be known to
+ // the GCM key store before the message can be decrypted.
+
+ IncomingMessage message;
+ message.data["encryption"] = kValidEncryptionHeader;
+ message.data["encryption_key"] = kValidEncryptionKeyHeader;
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
+ failure_reason());
+
+ std::string public_key;
+ encryption_provider()->GetPublicKey(
+ kExampleAppId,
+ base::Bind(&GCMEncryptionProviderTest::DidGetPublicKey,
+ base::Unretained(this), &public_key));
+
+ // Getting (or creating) the public key will be done asynchronously.
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(crypto::curve25519::kBytes, public_key.size());
+
+ ASSERT_NO_FATAL_FAILURE(Decrypt(message));
+ ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
+ EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
+ failure_reason());
+}
+
+TEST_F(GCMEncryptionProviderTest, EncryptionRoundTrip) {
+ // Performs a full round-trip of the encryption feature, including getting a
+ // public/private key-pair and performing the cryptographic operations. This
+ // is more of an integration test than a unit test.
+
+ KeyPair pair;
+ KeyPair server_pair;
+
+ // Retrieve the public/private key-pair immediately from the key store, given
+ // that the GCMEncryptionProvider will only share the public key with users.
+ // Also create a second pair, which will act as the server's keys.
+ encryption_provider()->key_store_->CreateKeys(
+ kExampleAppId,
+ base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
+ base::Unretained(this), &pair));
+
+ encryption_provider()->key_store_->CreateKeys(
+ std::string(kExampleAppId) + "-server",
+ base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
+ base::Unretained(this), &server_pair));
+
+ // Creating the public keys will be done asynchronously.
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(crypto::curve25519::kScalarBytes, pair.private_key().size());
+ ASSERT_EQ(crypto::curve25519::kBytes, pair.public_key().size());
+
+ ASSERT_EQ(crypto::curve25519::kScalarBytes, server_pair.private_key().size());
+ ASSERT_EQ(crypto::curve25519::kBytes, server_pair.public_key().size());
+
+ std::string salt;
+
+ // Creates a cryptographically secure salt of |salt_size| octets in size, and
+ // calculate the shared secret for the message.
+ crypto::RandBytes(base::WriteInto(&salt, 16 + 1), 16);
+
+ uint8_t shared_key[crypto::curve25519::kBytes];
+ crypto::curve25519::ScalarMult(
+ reinterpret_cast<const unsigned char*>(server_pair.private_key().data()),
+ reinterpret_cast<const unsigned char*>(pair.public_key().data()),
+ shared_key);
+
+ base::StringPiece shared_key_string_piece(
+ reinterpret_cast<char*>(shared_key), crypto::curve25519::kBytes);
+
+ IncomingMessage message;
+ size_t record_size;
+
+ // Encrypts the |kExampleMessage| using the generated shared key and the
+ // random |salt|, storing the result in |record_size| and the message.
+ GCMMessageCryptographer cryptographer;
+ ASSERT_TRUE(cryptographer.Encrypt(kExampleMessage, shared_key_string_piece,
+ salt, &record_size, &message.raw_data));
+
+ std::string encoded_salt, encoded_key;
+
+ // Compile the incoming GCM message, including the required headers.
+ Base64UrlEncode(salt, &encoded_salt);
+ Base64UrlEncode(server_pair.public_key(), &encoded_key);
+
+ std::stringstream encryption_header;
+ encryption_header << "rs=" << base::SizeTToString(record_size) << ";";
+ encryption_header << "salt=" << encoded_salt;
+
+ message.data["encryption"] = encryption_header.str();
+ message.data["encryption_key"] = "dh=" + encoded_key;
+
+ ASSERT_TRUE(encryption_provider()->IsEncryptedMessage(message));
+
+ // Decrypt the message, and expect everything to go wonderfully well.
+ ASSERT_NO_FATAL_FAILURE(Decrypt(message));
+ ASSERT_EQ(DECRYPTION_SUCCEEDED, decryption_result());
+
+ EXPECT_TRUE(decrypted_message().decrypted);
+ EXPECT_EQ(kExampleMessage, decrypted_message().raw_data);
+}
+
+} // namespace gcm
diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer.cc b/components/gcm_driver/crypto/gcm_message_cryptographer.cc
index 62b7247d..a3bcfad 100644
--- a/components/gcm_driver/crypto/gcm_message_cryptographer.cc
+++ b/components/gcm_driver/crypto/gcm_message_cryptographer.cc
@@ -23,13 +23,10 @@ const size_t kDefaultRecordSize = 4096;
// Key size, in bytes, of a valid AEAD_AES_128_GCM key.
const size_t kContentEncryptionKeySize = 16;
-// Salt size, in bytes, that will be used together with the key to create a
-// unique content encryption key for a given message.
-const size_t kSaltSize = 16;
-
} // namespace
const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16;
+const size_t GCMMessageCryptographer::kSaltSize = 16;
GCMMessageCryptographer::GCMMessageCryptographer() {}
diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer.h b/components/gcm_driver/crypto/gcm_message_cryptographer.h
index 58c44a8..e2b299e 100644
--- a/components/gcm_driver/crypto/gcm_message_cryptographer.h
+++ b/components/gcm_driver/crypto/gcm_message_cryptographer.h
@@ -32,6 +32,10 @@ namespace gcm {
// messages provided that a cryptographically-strong random salt is used.
class GCMMessageCryptographer {
public:
+ // Salt size, in bytes, that will be used together with the key to create a
+ // unique content encryption key for a given message.
+ static const size_t kSaltSize;
+
GCMMessageCryptographer();
~GCMMessageCryptographer();
diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc
index 4e17801..eed7244 100644
--- a/components/gcm_driver/gcm_driver.cc
+++ b/components/gcm_driver/gcm_driver.cc
@@ -13,8 +13,17 @@
namespace gcm {
+namespace {
+
const size_t kMaxSenders = 100;
+// TODO(peter): Implement an event for GCMAppHandlers that should be called
+// when decryption of an incoming message has failed.
+void DecryptionFailedCallback(
+ GCMEncryptionProvider::DecryptionFailure reason) {}
+
+} // namespace
+
InstanceIDHandler::InstanceIDHandler() {
}
@@ -258,6 +267,20 @@ void GCMDriver::ClearCallbacks() {
send_callbacks_.clear();
}
+void GCMDriver::DispatchMessage(const std::string& app_id,
+ const IncomingMessage& message) {
+ if (!encryption_provider_.IsEncryptedMessage(message)) {
+ GetAppHandler(app_id)->OnMessage(app_id, message);
+ return;
+ }
+
+ encryption_provider_.DecryptMessage(
+ app_id, message,
+ base::Bind(&GCMDriver::DispatchMessage,
+ weak_ptr_factory_.GetWeakPtr(), app_id),
+ base::Bind(&DecryptionFailedCallback));
+}
+
void GCMDriver::RegisterAfterUnregister(
const std::string& app_id,
const std::vector<std::string>& normalized_sender_ids,
diff --git a/components/gcm_driver/gcm_driver.h b/components/gcm_driver/gcm_driver.h
index 572c02d..cb5eae2 100644
--- a/components/gcm_driver/gcm_driver.h
+++ b/components/gcm_driver/gcm_driver.h
@@ -267,6 +267,13 @@ class GCMDriver {
void ClearCallbacks();
+ // Dispatches the OnMessage event to the app handler associated with |app_id|.
+ // If |message| has been encrypted, it will be decrypted asynchronously and
+ // dispatched when the decryption operation was successful. Otherwise, the
+ // |message| will be dispatched immediately to the handler for |app_id|.
+ void DispatchMessage(const std::string& app_id,
+ const IncomingMessage& message);
+
private:
// Common code shared by Unregister and UnregisterWithSenderId.
void UnregisterInternal(const std::string& app_id,
diff --git a/components/gcm_driver/gcm_driver_android.cc b/components/gcm_driver/gcm_driver_android.cc
index 84a8998..a0e04f7 100644
--- a/components/gcm_driver/gcm_driver_android.cc
+++ b/components/gcm_driver/gcm_driver_android.cc
@@ -88,7 +88,7 @@ void GCMDriverAndroid::OnMessageReceived(JNIEnv* env,
message.raw_data.assign(raw_data.begin(), raw_data.end());
}
- GetAppHandler(app_id)->OnMessage(app_id, message);
+ DispatchMessage(app_id, message);
}
void GCMDriverAndroid::OnMessagesDeleted(JNIEnv* env,
diff --git a/components/gcm_driver/gcm_driver_desktop.cc b/components/gcm_driver/gcm_driver_desktop.cc
index 8910e4b..282cb4b 100644
--- a/components/gcm_driver/gcm_driver_desktop.cc
+++ b/components/gcm_driver/gcm_driver_desktop.cc
@@ -1195,7 +1195,7 @@ void GCMDriverDesktop::MessageReceived(const std::string& app_id,
if (!gcm_started_)
return;
- GetAppHandler(app_id)->OnMessage(app_id, message);
+ DispatchMessage(app_id, message);
}
void GCMDriverDesktop::MessagesDeleted(const std::string& app_id) {