OLD | NEW |
---|---|
(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 | |
OLD | NEW |