OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |