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

Side by Side Diff: components/gcm_driver/crypto/gcm_message_cryptographer.cc

Issue 2716443002: Implement support for draft-ietf-webpush-encryption-08 (Closed)
Patch Set: fix windows^2 Created 3 years, 7 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
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h" 5 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
44 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03 44 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03
45 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02 45 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02
46 class WebPushEncryptionDraft03 46 class WebPushEncryptionDraft03
47 : public GCMMessageCryptographer::EncryptionScheme { 47 : public GCMMessageCryptographer::EncryptionScheme {
48 public: 48 public:
49 WebPushEncryptionDraft03() = default; 49 WebPushEncryptionDraft03() = default;
50 ~WebPushEncryptionDraft03() override = default; 50 ~WebPushEncryptionDraft03() override = default;
51 51
52 // GCMMessageCryptographer::EncryptionScheme implementation. 52 // GCMMessageCryptographer::EncryptionScheme implementation.
53 std::string DerivePseudoRandomKey( 53 std::string DerivePseudoRandomKey(
54 const base::StringPiece& /* recipient_public_key */,
55 const base::StringPiece& /* sender_public_key */,
54 const base::StringPiece& ecdh_shared_secret, 56 const base::StringPiece& ecdh_shared_secret,
55 const base::StringPiece& auth_secret) override { 57 const base::StringPiece& auth_secret) override {
56 std::stringstream info_stream; 58 const char kInfo[] = "Content-Encoding: auth";
eroman 2017/05/22 18:49:48 What about adding \0 at the end of the literal? I
Peter Beverloo 2017/05/23 18:17:11 Updated this to use sizeof() here: https://coderev
57 info_stream << "Content-Encoding: auth" << '\x00';
58 59
59 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(), 60 std::string info;
61 info.reserve(sizeof(kInfo) + 1);
62 info.append(kInfo);
63 info.append(1, '\0');
64
65 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info,
60 32, /* key_bytes_to_generate */ 66 32, /* key_bytes_to_generate */
61 0, /* iv_bytes_to_generate */ 67 0, /* iv_bytes_to_generate */
62 0 /* subkey_secret_bytes_to_generate */); 68 0 /* subkey_secret_bytes_to_generate */);
63 69
64 return hkdf.client_write_key().as_string(); 70 return hkdf.client_write_key().as_string();
65 } 71 }
66 72
67 // Creates the info parameter for an HKDF value for the given 73 // Creates the info parameter for an HKDF value for the given
68 // |content_encoding| in accordance with draft-ietf-webpush-encryption-03. 74 // |content_encoding| in accordance with draft-ietf-webpush-encryption-03.
69 // 75 //
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 // starts encrypting payloads for reasons other than testing. 122 // starts encrypting payloads for reasons other than testing.
117 std::string CreateRecord(const base::StringPiece& plaintext) override { 123 std::string CreateRecord(const base::StringPiece& plaintext) override {
118 std::string record; 124 std::string record;
119 record.reserve(sizeof(uint16_t) + plaintext.size()); 125 record.reserve(sizeof(uint16_t) + plaintext.size());
120 record.append(sizeof(uint16_t), '\x00'); 126 record.append(sizeof(uint16_t), '\x00');
121 127
122 plaintext.AppendToString(&record); 128 plaintext.AppendToString(&record);
123 return record; 129 return record;
124 } 130 }
125 131
132 // The |ciphertext| must be at least of size kAuthenticationTagBytes with two
133 // padding bytes, which is the case for an empty message with zero padding.
134 // The |record_size| must be large enough to use only one record.
135 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-03#secti on-2
136 bool ValidateCiphertextSize(size_t ciphertext_size,
137 size_t record_size) override {
138 return ciphertext_size >=
139 sizeof(uint16_t) +
140 GCMMessageCryptographer::kAuthenticationTagBytes &&
141 ciphertext_size <=
142 record_size + GCMMessageCryptographer::kAuthenticationTagBytes;
143 }
144
126 // The record padding in draft-ietf-webpush-encryption-03 is included at the 145 // The record padding in draft-ietf-webpush-encryption-03 is included at the
127 // beginning of the record. The first two bytes indicate the length of the 146 // beginning of the record. The first two bytes indicate the length of the
128 // padding. All padding bytes immediately follow, and must be set to zero. 147 // padding. All padding bytes immediately follow, and must be set to zero.
129 bool ValidateAndRemovePadding(base::StringPiece& record) override { 148 bool ValidateAndRemovePadding(base::StringPiece& record) override {
130 // Records must be at least two octets in size (to hold the padding). 149 // Records must be at least two octets in size (to hold the padding).
131 // Records that are smaller, i.e. a single octet, are invalid. 150 // Records that are smaller, i.e. a single octet, are invalid.
132 if (record.size() < sizeof(uint16_t)) 151 if (record.size() < sizeof(uint16_t))
133 return false; 152 return false;
134 153
135 // Records contain a two-byte, big-endian padding length followed by zero to 154 // Records contain a two-byte, big-endian padding length followed by zero to
(...skipping 14 matching lines...) Expand all
150 } 169 }
151 170
152 record.remove_prefix(padding_length); 171 record.remove_prefix(padding_length);
153 return true; 172 return true;
154 } 173 }
155 174
156 private: 175 private:
157 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03); 176 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03);
158 }; 177 };
159 178
179 // Implementation of draft 08 of the Web Push Encryption standard:
180 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-08
181 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-07
182 class WebPushEncryptionDraft08
183 : public GCMMessageCryptographer::EncryptionScheme {
184 public:
185 WebPushEncryptionDraft08() = default;
186 ~WebPushEncryptionDraft08() override = default;
187
188 // GCMMessageCryptographer::EncryptionScheme implementation.
189 std::string DerivePseudoRandomKey(
190 const base::StringPiece& recipient_public_key,
191 const base::StringPiece& sender_public_key,
192 const base::StringPiece& ecdh_shared_secret,
193 const base::StringPiece& auth_secret) override {
194 DCHECK_EQ(recipient_public_key.size(), 65u);
195 DCHECK_EQ(sender_public_key.size(), 65u);
196
197 const char kInfo[] = "WebPush: info";
eroman 2017/05/22 18:49:48 Same comment here, assuming it works.
198
199 std::string info;
200 info.reserve(sizeof(kInfo) + 1 + 65 + 65);
201 info.append(kInfo);
202 info.append(1, '\0');
203
204 recipient_public_key.AppendToString(&info);
205 sender_public_key.AppendToString(&info);
206
207 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info,
208 32, /* key_bytes_to_generate */
209 0, /* iv_bytes_to_generate */
210 0 /* subkey_secret_bytes_to_generate */);
211
212 return hkdf.client_write_key().as_string();
213 }
214
215 // The info string used for generating the content encryption key and the
216 // nonce was simplified in draft-ietf-webpush-encryption-08, because the
217 // public keys of both the recipient and the sender are now in the PRK.
218 std::string GenerateInfoForContentEncoding(
219 EncodingType type,
220 const base::StringPiece& /* recipient_public_key */,
221 const base::StringPiece& /* sender_public_key */) override {
222 std::stringstream info_stream;
223 info_stream << "Content-Encoding: ";
224
225 switch (type) {
226 case EncodingType::CONTENT_ENCRYPTION_KEY:
227 info_stream << "aes128gcm";
228 break;
229 case EncodingType::NONCE:
230 info_stream << "nonce";
231 break;
232 }
233
234 info_stream << '\x00';
235 return info_stream.str();
236 }
237
238 // draft-ietf-webpush-encryption-08 defines that the padding follows the
239 // plaintext of a message. A delimiter byte (0x02 for the final record) will
240 // be added, and then zero or more bytes of padding.
241 //
242 // TODO(peter): Add support for message padding if the GCMMessageCryptographer
243 // starts encrypting payloads for reasons other than testing.
244 std::string CreateRecord(const base::StringPiece& plaintext) override {
245 std::string record;
246 record.reserve(plaintext.size() + sizeof(uint8_t));
247 plaintext.AppendToString(&record);
248
249 record.append(sizeof(uint8_t), '\x02');
250 return record;
251 }
252
253 // The |ciphertext| must be at least of size kAuthenticationTagBytes with one
254 // padding delimiter, which is the case for an empty message with minimal
255 // padding. The |record_size| must be large enough to use only one record.
256 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-08#secti on-2
257 bool ValidateCiphertextSize(size_t ciphertext_size,
258 size_t record_size) override {
259 return ciphertext_size >=
260 sizeof(uint8_t) +
261 GCMMessageCryptographer::kAuthenticationTagBytes &&
262 ciphertext_size <=
263 record_size + GCMMessageCryptographer::kAuthenticationTagBytes;
264 }
265
266 // The record padding in draft-ietf-webpush-encryption-08 is included at the
267 // end of the record. The length is not defined, but all padding bytes must be
268 // zero until the delimiter (0x02) is found.
269 bool ValidateAndRemovePadding(base::StringPiece& record) override {
270 DCHECK_GE(record.size(), 1u);
271
272 size_t padding_length = 1;
273 for (; padding_length <= record.size(); ++padding_length) {
274 size_t offset = record.size() - padding_length;
275
276 if (record[offset] == 0x02 /* padding delimiter octet */)
277 break;
278
279 if (record[offset] != 0x00 /* valid padding byte */)
280 return false;
281 }
282
283 record.remove_suffix(padding_length);
284 return true;
285 }
286
287 private:
288 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft08);
289 };
290
160 } // namespace 291 } // namespace
161 292
162 const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16; 293 const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16;
163 const size_t GCMMessageCryptographer::kSaltSize = 16; 294 const size_t GCMMessageCryptographer::kSaltSize = 16;
164 295
165 GCMMessageCryptographer::GCMMessageCryptographer(Version version) { 296 GCMMessageCryptographer::GCMMessageCryptographer(Version version) {
166 switch (version) { 297 switch (version) {
167 case Version::DRAFT_03: 298 case Version::DRAFT_03:
168 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>(); 299 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>();
169 return; 300 return;
301 case Version::DRAFT_08:
302 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft08>();
303 return;
170 } 304 }
171 305
172 NOTREACHED(); 306 NOTREACHED();
173 } 307 }
174 308
175 GCMMessageCryptographer::~GCMMessageCryptographer() = default; 309 GCMMessageCryptographer::~GCMMessageCryptographer() = default;
176 310
177 bool GCMMessageCryptographer::Encrypt( 311 bool GCMMessageCryptographer::Encrypt(
178 const base::StringPiece& recipient_public_key, 312 const base::StringPiece& recipient_public_key,
179 const base::StringPiece& sender_public_key, 313 const base::StringPiece& sender_public_key,
180 const base::StringPiece& ecdh_shared_secret, 314 const base::StringPiece& ecdh_shared_secret,
181 const base::StringPiece& auth_secret, 315 const base::StringPiece& auth_secret,
182 const base::StringPiece& salt, 316 const base::StringPiece& salt,
183 const base::StringPiece& plaintext, 317 const base::StringPiece& plaintext,
184 size_t* record_size, 318 size_t* record_size,
185 std::string* ciphertext) const { 319 std::string* ciphertext) const {
186 DCHECK_EQ(recipient_public_key.size(), 65u); 320 DCHECK_EQ(recipient_public_key.size(), 65u);
187 DCHECK_EQ(sender_public_key.size(), 65u); 321 DCHECK_EQ(sender_public_key.size(), 65u);
188 DCHECK_EQ(ecdh_shared_secret.size(), 32u); 322 DCHECK_EQ(ecdh_shared_secret.size(), 32u);
189 DCHECK_EQ(auth_secret.size(), 16u); 323 DCHECK_EQ(auth_secret.size(), 16u);
190 DCHECK_EQ(salt.size(), 16u); 324 DCHECK_EQ(salt.size(), 16u);
191 DCHECK(record_size); 325 DCHECK(record_size);
192 DCHECK(ciphertext); 326 DCHECK(ciphertext);
193 327
194 std::string prk = encryption_scheme_->DerivePseudoRandomKey( 328 std::string prk = encryption_scheme_->DerivePseudoRandomKey(
195 ecdh_shared_secret, auth_secret); 329 recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret);
196 330
197 std::string content_encryption_key = DeriveContentEncryptionKey( 331 std::string content_encryption_key = DeriveContentEncryptionKey(
198 recipient_public_key, sender_public_key, prk, salt); 332 recipient_public_key, sender_public_key, prk, salt);
199 std::string nonce = 333 std::string nonce =
200 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); 334 DeriveNonce(recipient_public_key, sender_public_key, prk, salt);
201 335
202 std::string record = encryption_scheme_->CreateRecord(plaintext); 336 std::string record = encryption_scheme_->CreateRecord(plaintext);
203 std::string encrypted_record; 337 std::string encrypted_record;
204 338
205 if (!TransformRecord(Direction::ENCRYPT, record, content_encryption_key, 339 if (!TransformRecord(Direction::ENCRYPT, record, content_encryption_key,
(...skipping 22 matching lines...) Expand all
228 DCHECK_EQ(sender_public_key.size(), 65u); 362 DCHECK_EQ(sender_public_key.size(), 65u);
229 DCHECK_EQ(ecdh_shared_secret.size(), 32u); 363 DCHECK_EQ(ecdh_shared_secret.size(), 32u);
230 DCHECK_EQ(auth_secret.size(), 16u); 364 DCHECK_EQ(auth_secret.size(), 16u);
231 DCHECK_EQ(salt.size(), 16u); 365 DCHECK_EQ(salt.size(), 16u);
232 DCHECK(plaintext); 366 DCHECK(plaintext);
233 367
234 if (record_size <= 1) 368 if (record_size <= 1)
235 return false; 369 return false;
236 370
237 std::string prk = encryption_scheme_->DerivePseudoRandomKey( 371 std::string prk = encryption_scheme_->DerivePseudoRandomKey(
238 ecdh_shared_secret, auth_secret); 372 recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret);
239 373
240 std::string content_encryption_key = DeriveContentEncryptionKey( 374 std::string content_encryption_key = DeriveContentEncryptionKey(
241 recipient_public_key, sender_public_key, prk, salt); 375 recipient_public_key, sender_public_key, prk, salt);
242 376
243 std::string nonce = 377 std::string nonce =
244 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); 378 DeriveNonce(recipient_public_key, sender_public_key, prk, salt);
245 379
246 // The |ciphertext| must be at least of size kAuthenticationTagBytes, which 380 if (!encryption_scheme_->ValidateCiphertextSize(ciphertext.size(),
247 // is the case when an empty message with a zero padding length has been 381 record_size)) {
248 // received. The |record_size| must be large enough to use only one record.
249 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-03#secti on-2
250 if (ciphertext.size() < sizeof(uint16_t) + kAuthenticationTagBytes ||
251 ciphertext.size() > record_size + kAuthenticationTagBytes) {
252 return false; 382 return false;
253 } 383 }
254 384
255 std::string decrypted_record_string; 385 std::string decrypted_record_string;
256 if (!TransformRecord(Direction::DECRYPT, ciphertext, content_encryption_key, 386 if (!TransformRecord(Direction::DECRYPT, ciphertext, content_encryption_key,
257 nonce, &decrypted_record_string)) { 387 nonce, &decrypted_record_string)) {
258 return false; 388 return false;
259 } 389 }
260 390
261 DCHECK(!decrypted_record_string.empty()); 391 DCHECK(!decrypted_record_string.empty());
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
353 483
354 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02 484 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02
355 // defines that the result should be XOR'ed with the record's sequence number, 485 // defines that the result should be XOR'ed with the record's sequence number,
356 // however, Web Push encryption is limited to a single record per 486 // however, Web Push encryption is limited to a single record per
357 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03. 487 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03.
358 488
359 return hkdf.client_write_key().as_string(); 489 return hkdf.client_write_key().as_string();
360 } 490 }
361 491
362 } // namespace gcm 492 } // namespace gcm
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698