diff options
author | peter <peter@chromium.org> | 2015-10-09 03:18:52 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-09 10:19:37 +0000 |
commit | add31f6fb477348d2b6a755cc8b6107a7fcb5339 (patch) | |
tree | 836e2bbe1c1591e260cfc3a35158acbb14d3c939 /components/gcm_driver | |
parent | daae8f2139bd5681ff0bfbf620a86b645934cd34 (diff) | |
download | chromium_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.cc | 2 | ||||
-rw-r--r-- | components/gcm_driver/common/gcm_messages.h | 4 | ||||
-rw-r--r-- | components/gcm_driver/crypto/BUILD.gn | 1 | ||||
-rw-r--r-- | components/gcm_driver/crypto/gcm_encryption_provider.cc | 135 | ||||
-rw-r--r-- | components/gcm_driver/crypto/gcm_encryption_provider.h | 47 | ||||
-rw-r--r-- | components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc | 319 | ||||
-rw-r--r-- | components/gcm_driver/crypto/gcm_message_cryptographer.cc | 5 | ||||
-rw-r--r-- | components/gcm_driver/crypto/gcm_message_cryptographer.h | 4 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver.cc | 23 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver.h | 7 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver_android.cc | 2 | ||||
-rw-r--r-- | components/gcm_driver/gcm_driver_desktop.cc | 2 |
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) { |