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

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: 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 base::RunLoop().RunUntilIdle();
98
99 ASSERT_NE(decryption_result_, DECRYPTION_IDLE);
100 }
101
102 DecryptionResult decryption_result() { return decryption_result_; }
103
104 const IncomingMessage& decrypted_message() { return decrypted_message_; }
105
106 GCMEncryptionProvider::DecryptionFailure failure_reason() {
107 return failure_reason_;
108 }
109
110 GCMEncryptionProvider* encryption_provider() {
111 return encryption_provider_.get();
112 }
113
114 private:
115 void OnDecryptionSucceeded(const IncomingMessage& message) {
116 decryption_result_ = DECRYPTION_SUCCEEDED;
117 decrypted_message_ = message;
118 }
119
120 void OnDecryptionFailed(GCMEncryptionProvider::DecryptionFailure reason) {
121 decryption_result_ = DECRYPTION_FAILED;
122 failure_reason_ = reason;
123 }
124
125 base::MessageLoop message_loop_;
126 base::ScopedTempDir scoped_temp_dir_;
127
128 scoped_ptr<GCMEncryptionProvider> encryption_provider_;
129
130 DecryptionResult decryption_result_ = DECRYPTION_IDLE;
131 GCMEncryptionProvider::DecryptionFailure failure_reason_ =
132 GCMEncryptionProvider::DECRYPTION_FAILURE_UNKNOWN;
133
134 IncomingMessage decrypted_message_;
135 };
136
137 TEST_F(GCMEncryptionProviderTest, IsEncryptedMessage) {
138 // Both the Encryption and Encryption-Key headers must be present, and the raw
139 // data must be non-empty for a message to be considered encrypted.
140
141 IncomingMessage empty_message;
142 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(empty_message));
143
144 IncomingMessage single_header_message;
145 single_header_message.data["encryption"] = "";
146 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
147 single_header_message));
148
149 IncomingMessage double_header_message;
150 double_header_message.data["encryption"] = "";
151 double_header_message.data["encryption_key"] = "";
152 EXPECT_FALSE(encryption_provider()->IsEncryptedMessage(
153 double_header_message));
154
155 IncomingMessage double_header_with_data_message;
156 double_header_with_data_message.data["encryption"] = "";
157 double_header_with_data_message.data["encryption_key"] = "";
158 double_header_with_data_message.raw_data = "foo";
159 EXPECT_TRUE(encryption_provider()->IsEncryptedMessage(
160 double_header_with_data_message));
161 }
162
163 TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionHeaderParsing) {
164 // The Encryption header must be parsable and contain valid values.
165 // Note that this is more extensively tested in EncryptionHeaderParsersTest.
166
167 IncomingMessage invalid_message;
168 invalid_message.data["encryption"] = kInvalidEncryptionHeader;
169 invalid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
170
171 ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
172 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
173 EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
174 failure_reason());
175
176 IncomingMessage valid_message;
177 valid_message.data["encryption"] = kValidEncryptionHeader;
178 valid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
179
180 ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
181 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
182 EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_HEADER,
183 failure_reason());
184 }
185
186 TEST_F(GCMEncryptionProviderTest, VerifiesEncryptionKeyHeaderParsing) {
187 // The Encryption-Key header must be parsable and contain valid values.
188 // Note that this is more extensively tested in EncryptionHeaderParsersTest.
189
190 IncomingMessage invalid_message;
191 invalid_message.data["encryption"] = kValidEncryptionHeader;
192 invalid_message.data["encryption_key"] = kInvalidEncryptionKeyHeader;
193
194 ASSERT_NO_FATAL_FAILURE(Decrypt(invalid_message));
195 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
196 EXPECT_EQ(
197 GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
198 failure_reason());
199
200 IncomingMessage valid_message;
201 valid_message.data["encryption"] = kInvalidEncryptionHeader;
202 valid_message.data["encryption_key"] = kValidEncryptionKeyHeader;
203
204 ASSERT_NO_FATAL_FAILURE(Decrypt(valid_message));
205 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
206 EXPECT_NE(
207 GCMEncryptionProvider::DECRYPTION_FAILURE_INVALID_ENCRYPTION_KEY_HEADER,
208 failure_reason());
209 }
210
211 TEST_F(GCMEncryptionProviderTest, VerifiesExistingKeys) {
212 // When both headers are valid, the encryption keys still must be known to
213 // the GCM key store before the message can be decrypted.
214
215 IncomingMessage message;
216 message.data["encryption"] = kValidEncryptionHeader;
217 message.data["encryption_key"] = kValidEncryptionKeyHeader;
218
219 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
220 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
221 EXPECT_EQ(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
222 failure_reason());
223
224 std::string public_key;
225 encryption_provider()->GetPublicKey(
226 kExampleAppId,
227 base::Bind(&GCMEncryptionProviderTest::DidGetPublicKey,
228 base::Unretained(this), &public_key));
229
230 base::RunLoop().RunUntilIdle();
231
232 ASSERT_EQ(crypto::curve25519::kBytes, public_key.size());
233
234 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
235 ASSERT_EQ(DECRYPTION_FAILED, decryption_result());
236 EXPECT_NE(GCMEncryptionProvider::DECRYPTION_FAILURE_NO_KEYS,
237 failure_reason());
238 }
239
240 TEST_F(GCMEncryptionProviderTest, EncryptionRoundTrip) {
241 // Performs a full round-trip of the encryption feature, including getting a
242 // public/private key-pair and performing the cryptographic operations. This
243 // is more of an integration test than a unit test.
244
245 KeyPair pair;
246 KeyPair server_pair;
247
248 // Retrieve the public/private key-pair immediately from the key store, given
249 // that the GCMEncryptionProvider will only share the public key with users.
250 // Also create a second pair, which will act as the server's keys.
251 encryption_provider()->key_store_->CreateKeys(
252 kExampleAppId,
253 base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
254 base::Unretained(this), &pair));
255
256 encryption_provider()->key_store_->CreateKeys(
257 std::string(kExampleAppId) + "-server",
258 base::Bind(&GCMEncryptionProviderTest::DidCreateKeys,
259 base::Unretained(this), &server_pair));
260
261 base::RunLoop().RunUntilIdle();
Michael van Ouwerkerk 2015/10/02 14:30:42 nit: document what these loop runs are for.
Peter Beverloo 2015/10/02 15:01:22 Done.
262
263 ASSERT_EQ(crypto::curve25519::kScalarBytes, pair.private_key().size());
264 ASSERT_EQ(crypto::curve25519::kBytes, pair.public_key().size());
265
266 ASSERT_EQ(crypto::curve25519::kScalarBytes, server_pair.private_key().size());
267 ASSERT_EQ(crypto::curve25519::kBytes, server_pair.public_key().size());
268
269 std::string salt;
270
271 // Creates a cryptographically secure salt of |salt_size| octets in size, and
272 // calculate the shared secret for the message.
273 crypto::RandBytes(base::WriteInto(&salt, 16 + 1), 16);
274
275 uint8_t shared_key[crypto::curve25519::kBytes];
276 crypto::curve25519::ScalarMult(
277 reinterpret_cast<const unsigned char*>(server_pair.private_key().data()),
278 reinterpret_cast<const unsigned char*>(pair.public_key().data()),
279 shared_key);
280
281 base::StringPiece shared_key_string_piece(
282 reinterpret_cast<char*>(shared_key), crypto::curve25519::kBytes);
283
284 IncomingMessage message;
285 size_t record_size;
286
287 // Encrypts the |kExampleMessage| using the generated shared key and the
288 // random |salt|, storing the result in |record_size| and the message.
289 GCMMessageCryptographer cryptographer;
290 ASSERT_TRUE(cryptographer.Encrypt(kExampleMessage, shared_key_string_piece,
291 salt, &record_size, &message.raw_data));
292
293 std::string encoded_salt, encoded_key;
294
295 // Compile the incoming GCM message, including the required headers.
296 Base64UrlEncode(salt, &encoded_salt);
297 Base64UrlEncode(server_pair.public_key(), &encoded_key);
298
299 std::stringstream encryption_header;
300 encryption_header << "rs=" << base::SizeTToString(record_size) << ";";
301 encryption_header << "salt=" << encoded_salt;
302
303 message.data["encryption"] = encryption_header.str();
304 message.data["encryption_key"] = "dh=" + encoded_key;
305
306 ASSERT_TRUE(encryption_provider()->IsEncryptedMessage(message));
307
308 // Decrypt the message, and expect everything to go wonderfully well.
309 ASSERT_NO_FATAL_FAILURE(Decrypt(message));
310 ASSERT_EQ(DECRYPTION_SUCCEEDED, decryption_result());
311
312 EXPECT_TRUE(decrypted_message().decrypted);
313 EXPECT_EQ(kExampleMessage, decrypted_message().raw_data);
314 }
315
316 } // namespace gcm
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698