Chromium Code Reviews| 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 |