Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(955)

Unified Diff: components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc

Issue 1243563002: Teach the GCM Driver how to decrypt incoming messages. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@gcm-push-keys
Patch Set: address comment Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc
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 0000000000000000000000000000000000000000..14a6f714798ac7b0137d7a7b6e9bd8d7867a51ea
--- /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
« no previous file with comments | « components/gcm_driver/crypto/gcm_encryption_provider.cc ('k') | components/gcm_driver/crypto/gcm_message_cryptographer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698