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 std::stringstream info_stream; |
| 57 info_stream << "Content-Encoding: auth" << '\x00'; | 59 info_stream << "Content-Encoding: auth" << '\x00'; |
|
Peter Beverloo
2017/05/18 17:10:21
Updated this too per the previous CL.
| |
| 58 | 60 |
| 59 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(), | 61 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(), |
| 60 32, /* key_bytes_to_generate */ | 62 32, /* key_bytes_to_generate */ |
| 61 0, /* iv_bytes_to_generate */ | 63 0, /* iv_bytes_to_generate */ |
| 62 0 /* subkey_secret_bytes_to_generate */); | 64 0 /* subkey_secret_bytes_to_generate */); |
| 63 | 65 |
| 64 return hkdf.client_write_key().as_string(); | 66 return hkdf.client_write_key().as_string(); |
| 65 } | 67 } |
| 66 | 68 |
| 67 // Creates the info parameter for an HKDF value for the given | 69 // Creates the info parameter for an HKDF value for the given |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 } | 152 } |
| 151 | 153 |
| 152 record.remove_prefix(padding_length); | 154 record.remove_prefix(padding_length); |
| 153 return true; | 155 return true; |
| 154 } | 156 } |
| 155 | 157 |
| 156 private: | 158 private: |
| 157 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03); | 159 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03); |
| 158 }; | 160 }; |
| 159 | 161 |
| 162 // Implementation of draft 08 of the Web Push Encryption standard: | |
| 163 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-08 | |
| 164 // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-07 | |
| 165 class WebPushEncryptionDraft08 | |
| 166 : public GCMMessageCryptographer::EncryptionScheme { | |
| 167 public: | |
| 168 WebPushEncryptionDraft08() = default; | |
| 169 ~WebPushEncryptionDraft08() override = default; | |
| 170 | |
| 171 // GCMMessageCryptographer::EncryptionScheme implementation. | |
| 172 std::string DerivePseudoRandomKey( | |
| 173 const base::StringPiece& recipient_public_key, | |
| 174 const base::StringPiece& sender_public_key, | |
| 175 const base::StringPiece& ecdh_shared_secret, | |
| 176 const base::StringPiece& auth_secret) override { | |
| 177 std::stringstream info_stream; | |
|
eroman
2017/05/16 21:03:27
same comment as on other CLs -- i don't think stri
Peter Beverloo
2017/05/18 17:10:21
Done.
| |
| 178 info_stream << "WebPush: info" << '\x00'; | |
| 179 info_stream << recipient_public_key; | |
| 180 info_stream << sender_public_key; | |
| 181 | |
| 182 const std::string info = info_stream.str(); | |
| 183 DCHECK_EQ(info.size(), 144u); | |
| 184 | |
| 185 crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(), | |
| 186 32, /* key_bytes_to_generate */ | |
| 187 0, /* iv_bytes_to_generate */ | |
| 188 0 /* subkey_secret_bytes_to_generate */); | |
| 189 | |
| 190 return hkdf.client_write_key().as_string(); | |
| 191 } | |
| 192 | |
| 193 // The info string used for generating the content encryption key and the | |
| 194 // nonce was simplified in draft-ietf-webpush-encryption-08, because the | |
| 195 // public keys of both the recipient and the sender are now in the PRK. | |
| 196 std::string GenerateInfoForContentEncoding( | |
| 197 EncodingType type, | |
| 198 const base::StringPiece& /* recipient_public_key */, | |
| 199 const base::StringPiece& /* sender_public_key */) override { | |
| 200 std::stringstream info_stream; | |
| 201 info_stream << "Content-Encoding: "; | |
| 202 | |
| 203 switch (type) { | |
| 204 case EncodingType::CONTENT_ENCRYPTION_KEY: | |
| 205 info_stream << "aes128gcm"; | |
| 206 break; | |
| 207 case EncodingType::NONCE: | |
| 208 info_stream << "nonce"; | |
| 209 break; | |
| 210 } | |
| 211 | |
| 212 info_stream << '\x00'; | |
| 213 return info_stream.str(); | |
| 214 } | |
| 215 | |
| 216 // draft-ietf-webpush-encryption-08 defines that the padding follows the | |
| 217 // plaintext of a message. A delimiter byte (0x02 for the final record) will | |
| 218 // be added, and then zero or more bytes of padding. | |
| 219 // | |
| 220 // TODO(peter): Add support for message padding if the GCMMessageCryptographer | |
| 221 // starts encrypting payloads for reasons other than testing. | |
| 222 std::string CreateRecord(const base::StringPiece& plaintext) override { | |
| 223 std::string record; | |
| 224 record.reserve(plaintext.size() + sizeof(uint8_t)); | |
| 225 plaintext.AppendToString(&record); | |
| 226 | |
| 227 record.append(sizeof(uint8_t), '\x02'); | |
| 228 return record; | |
| 229 } | |
| 230 | |
| 231 // The record padding in draft-ietf-webpush-encryption-08 is included at the | |
| 232 // end of the record. The length is not defined, but all padding bytes must be | |
| 233 // zero until the delimiter (0x02) is found. | |
| 234 bool ValidateAndRemovePadding(base::StringPiece& record) override { | |
| 235 size_t padding_length = 1; | |
| 236 for (; padding_length < record.size(); ++padding_length) { | |
| 237 size_t offset = record.size() - padding_length; | |
| 238 | |
| 239 if (record[offset] == 0x02 /* padding delimiter octet */) | |
| 240 break; | |
| 241 | |
| 242 if (record[offset] != 0x00 /* valid padding byte */) | |
| 243 return false; | |
| 244 } | |
| 245 | |
| 246 record.remove_suffix(padding_length); | |
|
eroman
2017/05/16 21:03:27
Is this correct in the case where we don't loop?
Peter Beverloo
2017/05/18 17:10:21
I added a DCHECK to verify that record.size() >= 1
| |
| 247 return true; | |
| 248 } | |
| 249 | |
| 250 private: | |
| 251 DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft08); | |
| 252 }; | |
| 253 | |
| 160 } // namespace | 254 } // namespace |
| 161 | 255 |
| 162 const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16; | 256 const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16; |
| 163 const size_t GCMMessageCryptographer::kSaltSize = 16; | 257 const size_t GCMMessageCryptographer::kSaltSize = 16; |
| 164 | 258 |
| 165 GCMMessageCryptographer::GCMMessageCryptographer(Version version) { | 259 GCMMessageCryptographer::GCMMessageCryptographer(Version version) { |
| 166 switch (version) { | 260 switch (version) { |
| 167 case Version::DRAFT_03: | 261 case Version::DRAFT_03: |
| 168 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>(); | 262 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>(); |
| 169 return; | 263 return; |
| 264 case Version::DRAFT_08: | |
| 265 encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft08>(); | |
| 266 return; | |
| 170 } | 267 } |
| 171 | 268 |
| 172 NOTREACHED(); | 269 NOTREACHED(); |
| 173 } | 270 } |
| 174 | 271 |
| 175 GCMMessageCryptographer::~GCMMessageCryptographer() = default; | 272 GCMMessageCryptographer::~GCMMessageCryptographer() = default; |
| 176 | 273 |
| 177 bool GCMMessageCryptographer::Encrypt( | 274 bool GCMMessageCryptographer::Encrypt( |
| 178 const base::StringPiece& recipient_public_key, | 275 const base::StringPiece& recipient_public_key, |
| 179 const base::StringPiece& sender_public_key, | 276 const base::StringPiece& sender_public_key, |
| 180 const base::StringPiece& ecdh_shared_secret, | 277 const base::StringPiece& ecdh_shared_secret, |
| 181 const base::StringPiece& auth_secret, | 278 const base::StringPiece& auth_secret, |
| 182 const base::StringPiece& salt, | 279 const base::StringPiece& salt, |
| 183 const base::StringPiece& plaintext, | 280 const base::StringPiece& plaintext, |
| 184 size_t* record_size, | 281 size_t* record_size, |
| 185 std::string* ciphertext) const { | 282 std::string* ciphertext) const { |
| 186 DCHECK_EQ(recipient_public_key.size(), 65u); | 283 DCHECK_EQ(recipient_public_key.size(), 65u); |
| 187 DCHECK_EQ(sender_public_key.size(), 65u); | 284 DCHECK_EQ(sender_public_key.size(), 65u); |
| 188 DCHECK_EQ(ecdh_shared_secret.size(), 32u); | 285 DCHECK_EQ(ecdh_shared_secret.size(), 32u); |
| 189 DCHECK_EQ(auth_secret.size(), 16u); | 286 DCHECK_EQ(auth_secret.size(), 16u); |
| 190 DCHECK_EQ(salt.size(), 16u); | 287 DCHECK_EQ(salt.size(), 16u); |
| 191 DCHECK(record_size); | 288 DCHECK(record_size); |
| 192 DCHECK(ciphertext); | 289 DCHECK(ciphertext); |
| 193 | 290 |
| 194 std::string prk = encryption_scheme_->DerivePseudoRandomKey( | 291 std::string prk = encryption_scheme_->DerivePseudoRandomKey( |
| 195 ecdh_shared_secret, auth_secret); | 292 recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret); |
| 196 | 293 |
| 197 std::string content_encryption_key = DeriveContentEncryptionKey( | 294 std::string content_encryption_key = DeriveContentEncryptionKey( |
| 198 recipient_public_key, sender_public_key, prk, salt); | 295 recipient_public_key, sender_public_key, prk, salt); |
| 199 std::string nonce = | 296 std::string nonce = |
| 200 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); | 297 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); |
| 201 | 298 |
| 202 std::string record = encryption_scheme_->CreateRecord(plaintext); | 299 std::string record = encryption_scheme_->CreateRecord(plaintext); |
| 203 std::string encrypted_record; | 300 std::string encrypted_record; |
| 204 | 301 |
| 205 if (!TransformRecord(Direction::ENCRYPT, record, content_encryption_key, | 302 if (!TransformRecord(Direction::ENCRYPT, record, content_encryption_key, |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 228 DCHECK_EQ(sender_public_key.size(), 65u); | 325 DCHECK_EQ(sender_public_key.size(), 65u); |
| 229 DCHECK_EQ(ecdh_shared_secret.size(), 32u); | 326 DCHECK_EQ(ecdh_shared_secret.size(), 32u); |
| 230 DCHECK_EQ(auth_secret.size(), 16u); | 327 DCHECK_EQ(auth_secret.size(), 16u); |
| 231 DCHECK_EQ(salt.size(), 16u); | 328 DCHECK_EQ(salt.size(), 16u); |
| 232 DCHECK(plaintext); | 329 DCHECK(plaintext); |
| 233 | 330 |
| 234 if (record_size <= 1) | 331 if (record_size <= 1) |
| 235 return false; | 332 return false; |
| 236 | 333 |
| 237 std::string prk = encryption_scheme_->DerivePseudoRandomKey( | 334 std::string prk = encryption_scheme_->DerivePseudoRandomKey( |
| 238 ecdh_shared_secret, auth_secret); | 335 recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret); |
| 239 | 336 |
| 240 std::string content_encryption_key = DeriveContentEncryptionKey( | 337 std::string content_encryption_key = DeriveContentEncryptionKey( |
| 241 recipient_public_key, sender_public_key, prk, salt); | 338 recipient_public_key, sender_public_key, prk, salt); |
| 242 | 339 |
| 243 std::string nonce = | 340 std::string nonce = |
| 244 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); | 341 DeriveNonce(recipient_public_key, sender_public_key, prk, salt); |
| 245 | 342 |
| 246 // The |ciphertext| must be at least of size kAuthenticationTagBytes, which | 343 // The |ciphertext| must be at least of size kAuthenticationTagBytes, which |
| 247 // is the case when an empty message with a zero padding length has been | 344 // is the case when an empty message with a zero padding length has been |
| 248 // received. The |record_size| must be large enough to use only one record. | 345 // received. The |record_size| must be large enough to use only one record. |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 352 0 /* subkey_secret_bytes_to_generate */); | 449 0 /* subkey_secret_bytes_to_generate */); |
| 353 | 450 |
| 354 // draft-ietf-httpbis-encryption-encoding defines that the result should | 451 // draft-ietf-httpbis-encryption-encoding defines that the result should |
| 355 // be XOR'ed with the record's sequence number, however, Web Push encryption | 452 // be XOR'ed with the record's sequence number, however, Web Push encryption |
| 356 // is limited to a single record per draft-ietf-webpush-encryption. | 453 // is limited to a single record per draft-ietf-webpush-encryption. |
| 357 | 454 |
| 358 return hkdf.client_write_key().as_string(); | 455 return hkdf.client_write_key().as_string(); |
| 359 } | 456 } |
| 360 | 457 |
| 361 } // namespace gcm | 458 } // namespace gcm |
| OLD | NEW |