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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/gcm_driver/crypto/gcm_encryption_provider.h"
6
7 #include <sstream>
8 #include <string>
9
10 #include "base/base64.h"
11 #include "base/bind.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/string_util.h"
18 #include "components/gcm_driver/common/gcm_messages.h"
19 #include "components/gcm_driver/crypto/gcm_key_store.h"
20 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
21 #include "crypto/curve25519.h"
22 #include "crypto/random.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24
25 namespace gcm {
26 namespace {
27
28 const char kExampleAppId[] = "my-app-id";
29 const char kExampleMessage[] = "Hello, world, this is the GCM Driver!";
30
31 const char kValidEncryptionHeader[] =
32 "keyid=foo;salt=MTIzNDU2Nzg5MDEyMzQ1Ng;rs=1024";
33 const char kInvalidEncryptionHeader[] = "keyid";
34
35 const char kValidEncryptionKeyHeader[] =
36 "keyid=foo;dh=NjU0MzIxMDk4NzY1NDMyMTEyMzQ1Njc4OTAxMjM0NTY";
37 const char kInvalidEncryptionKeyHeader[] = "keyid";
38
39 // TODO(peter): Unify the Base64Url implementations. https://crbug.com/536745.
40 void Base64UrlEncode(const std::string& decoded_input,
41 std::string* encoded_output) {
42 base::Base64Encode(decoded_input, encoded_output);
43 base::ReplaceChars(*encoded_output, "+", "-", encoded_output);
44 base::ReplaceChars(*encoded_output, "/", "_", encoded_output);
45 }
46
47 } // namespace
48
49 class GCMEncryptionProviderTest : public ::testing::Test {
50 public:
51 void SetUp() override {
52 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
53
54 encryption_provider_.reset(new GCMEncryptionProvider);
55 encryption_provider_->Init(scoped_temp_dir_.path(),
56 message_loop_.task_runner());
57 }
58
59 void TearDown() override {
60 encryption_provider_.reset();
61
62 // |encryption_provider_| owns a ProtoDatabaseImpl whose destructor deletes
63 // the underlying LevelDB database on the task runner.
64 base::RunLoop().RunUntilIdle();
65 }
66
67 // To be used as a callback for GCMEncryptionProvider::GetPublicKey().
68 void DidGetPublicKey(std::string* key_out, const std::string& key) {
69 *key_out = key;
70 }
71
72 // To be used as a callback for GCMKeyStore::CreateKeys().
73 void DidCreateKeys(KeyPair* pair_out, const KeyPair& pair) {
74 *pair_out = pair;
75 }
76
77 protected:
78 // Tri-state enumaration listing whether the decryption operation is idle
79 // (hasn't started yet), succeeded or failed.
80 enum DecryptionResult {
81 DECRYPTION_IDLE,
82 DECRYPTION_SUCCEEDED,
83 DECRYPTION_FAILED
84 };
85
86 // Decrypts the |message| and then synchronously waits until either the
87 // success or failure callbacks has been invoked.
88 void Decrypt(const IncomingMessage& message) {
89 decryption_result_ = DECRYPTION_IDLE;
90 encryption_provider_->DecryptMessage(
91 kExampleAppId, message,
92 base::Bind(&GCMEncryptionProviderTest::OnDecryptionSucceeded,
93 base::Unretained(this)),
94 base::Bind(&GCMEncryptionProviderTest::OnDecryptionFailed,
95 base::Unretained(this)));
96
97 // The encryption keys will be read asynchronously.
98 base::RunLoop().RunUntilIdle();
99
100 ASSERT_NE(decryption_result_, DECRYPTION_IDLE);
101 }
102
103 DecryptionResult decryption_result() { return decryption_result_; }
104
105 const IncomingMessage& decrypted_message() { return decrypted_message_; }
106
107 GCMEncryptionProvider::DecryptionFailure failure_reason() {
108 return failure_reason_;
109 }
110
111 GCMEncryptionProvider* encryption_provider() {
112 return encryption_provider_.get();
113 }
114
115 private:
116 void OnDecryptionSucceeded(const IncomingMessage& message) {
117 decryption_result_ = DECRYPTION_SUCCEEDED;
118 decrypted_message_ = message;
119 }
120
121 void OnDecryptionFailed(GCMEncryptionProvider::DecryptionFailure reason) {
122 decryption_result_ = DECRYPTION_FAILED;
123 failure_reason_ = reason;
124 }
125
126 base::MessageLoop message_loop_;
127 base::ScopedTempDir scoped_temp_dir_;
128
129 scoped_ptr<GCMEncryptionProvider> encryption_provider_;
130
131 DecryptionResult decryption_result_ = DECRYPTION_IDLE;
132 GCMEncryptionProvider::DecryptionFailure failure_reason_ =
133 GCMEncryptionProvider::DECRYPTION_FAILURE_UNKNOWN;
134
135 IncomingMessage decrypted_message_;
136 };
137
138 TEST_F(GCMEncryptionProviderTest, IsEncryptedMessage) {
139 // Both the Encryption and Encryption-Key headers must be present, and the raw
140 // data must be non-empty for a message to be considered encrypted.
141
142 IncomingMessage empty_message;
143 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(empty_message));
144
145 IncomingMessage single_header_message;
146 single_header_message.data["encryption"] = "";
147 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
148 single_header_message));
149
150 IncomingMessage double_header_message;
151 double_header_message.data["encryption"] = "";
152 double_header_message.data["encryption_key"] = "";
153 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
154 double_header_message));
155
156 IncomingMessage double_header_with_data_message;
157 double_header_with_data_message.data["encryption"] = "";
158 double_header_with_data_message.data["encryption_key"] = "";
159 double_header_with_data_message.raw_data = "foo";
160 EXPECT_TRUE(encryption_provider()->IsEncryptedMessage(
161 double_header_with_data_message));
162 }
163
164 TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionHeaderParsing) {
165 // The Encryption header must be parsable and contain valid values.
166 // Note that this is more extensively tested in EncryptionHeaderParsersTest.
167
168 IncomingMessage invalid_message;
169 invalid_message.data["encryption"] = kInvalidEncryptionHeader;
170 invalid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
171
172 ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
173 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
174 EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
175 failure_reason());
176
177 IncomingMessage valid_message;
178 valid_message.data["encryption"] = kValidEncryptionHeader;
179 valid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
180
181 ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
182 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
183 EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
184 failure_reason());
185 }
186
187 TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionKeyHeaderParsing) {
188 // The Encryption-Key header must be parsable and contain valid values.
189 // Note that this is more extensively tested in EncryptionHeaderParsersTest.
190
191 IncomingMessage invalid_message;
192 invalid_message.data["encryption"] = kValidEncryptionHeader;
193 invalid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
194
195 ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
196 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
197 EXPECT_EQ(
198 GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
199 failure_reason());
200
201 IncomingMessage valid_message;
202 valid_message.data["encryption"] = kInvalidEncryptionHeader;
203 valid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
204
205 ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
206 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
207 EXPECT_NE(
208 GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
209 failure_reason());
210 }
211
212 TEST_F(GCMEncryptionProviderTest, VerifiesExistingKeys) {
213 // When both headers are valid, the encryption keys still must be known to
214 // the GCM key store before the message can be decrypted.
215
216 IncomingMessage message;
217 message.data["encryption"] = kValidEncryptionHeader;
218 message.data["encryption_key"] = kValidEncryptionKeyHeader;
219
220 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
221 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
222 EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
223 failure_reason());
224
225 std::string public_key;
226 encryption_provider()->GetPublicKey(
227 kExampleAppId,
228 base::Bind(&GCMEncryptionProviderTest::DidGetPublicKey,
229 base::Unretained(this), &public_key));
230
231 // Getting (or creating) the public key will be done asynchronously.
232 base::RunLoop().RunUntilIdle();
233
234 ASSERT_EQ(crypto::curve25519::kBytes, public_key.size());
235
236 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
237 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
238 EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
239 failure_reason());
240 }
241
242 TEST_F(GCMEncryptionProviderTest, EncryptionRoundTrip) {
243 // Performs a full round-trip of the encryption feature, including getting a
244 // public/private key-pair and performing the cryptographic operations. This
245 // is more of an integration test than a unit test.
246
247 KeyPair pair;
248 KeyPair server_pair;
249
250 // Retrieve the public/private key-pair immediately from the key store, given
251 // that the GCMEncryptionProvider will only share the public key with users.
252 // Also create a second pair, which will act as the server's keys.
253 encryption_provider()->key_store_->CreateKeys(
254 kExampleAppId,
255 base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
256 base::Unretained(this), &pair));
257
258 encryption_provider()->key_store_->CreateKeys(
259 std::string(kExampleAppId) + "-server",
260 base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
261 base::Unretained(this), &server_pair));
262
263 // Creating the public keys will be done asynchronously.
264 base::RunLoop().RunUntilIdle();
265
266 ASSERT_EQ(crypto::curve25519::kScalarBytes, pair.private_key().size());
267 ASSERT_EQ(crypto::curve25519::kBytes, pair.public_key().size());
268
269 ASSERT_EQ(crypto::curve25519::kScalarBytes, server_pair.private_key().size());
270 ASSERT_EQ(crypto::curve25519::kBytes, server_pair.public_key().size());
271
272 std::string salt;
273
274 // Creates a cryptographically secure salt of |salt_size| octets in size, and
275 // calculate the shared secret for the message.
276 crypto::RandBytes(base::WriteInto(&salt, 16 + 1), 16);
277
278 uint8_t shared_key[crypto::curve25519::kBytes];
279 crypto::curve25519::ScalarMult(
280 reinterpret_cast<const unsigned char*>(server_pair.private_key().data()),
281 reinterpret_cast<const unsigned char*>(pair.public_key().data()),
282 shared_key);
283
284 base::StringPiece shared_key_string_piece(
285 reinterpret_cast<char*>(shared_key), crypto::curve25519::kBytes);
286
287 IncomingMessage message;
288 size_t record_size;
289
290 // Encrypts the |kExampleMessage| using the generated shared key and the
291 // random |salt|, storing the result in |record_size| and the message.
292 GCMMessageCryptographer cryptographer;
293 ASSERT_TRUE(cryptographer.Encrypt(kExampleMessage, shared_key_string_piece,
294 salt, &record_size, &message.raw_data));
295
296 std::string encoded_salt, encoded_key;
297
298 // Compile the incoming GCM message, including the required headers.
299 Base64UrlEncode(salt, &encoded_salt);
300 Base64UrlEncode(server_pair.public_key(), &encoded_key);
301
302 std::stringstream encryption_header;
303 encryption_header << "rs=" << base::SizeTToString(record_size) << ";";
304 encryption_header << "salt=" << encoded_salt;
305
306 message.data["encryption"] = encryption_header.str();
307 message.data["encryption_key"] = "dh=" + encoded_key;
308
309 ASSERT_TRUE(encryption_provider()->IsEncryptedMessage(message));
310
311 // Decrypt the message, and expect everything to go wonderfully well.
312 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
313 ASSERT_EQ(DECRYPTION_SUCCEEDED, decryption_result());
314
315 EXPECT_TRUE(decrypted_message().decrypted);
316 EXPECT_EQ(kExampleMessage, decrypted_message().raw_data);
317 }
318
319 } // namespace gcm
OLDNEW
« 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