| 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_encryption_provider.h" | 5 #include "components/gcm_driver/crypto/gcm_encryption_provider.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/base64.h" | 9 #include "base/base64.h" |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "components/gcm_driver/common/gcm_messages.h" | 12 #include "components/gcm_driver/common/gcm_messages.h" |
| 13 #include "components/gcm_driver/crypto/encryption_header_parsers.h" | 13 #include "components/gcm_driver/crypto/encryption_header_parsers.h" |
| 14 #include "components/gcm_driver/crypto/gcm_key_store.h" | 14 #include "components/gcm_driver/crypto/gcm_key_store.h" |
| 15 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h" | 15 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h" |
| 16 #include "components/gcm_driver/crypto/message_payload_parser.h" |
| 16 #include "components/gcm_driver/crypto/p256_key_util.h" | 17 #include "components/gcm_driver/crypto/p256_key_util.h" |
| 17 #include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h" | 18 #include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h" |
| 18 | 19 |
| 19 namespace gcm { | 20 namespace gcm { |
| 20 | 21 |
| 21 namespace { | 22 namespace { |
| 22 | 23 |
| 23 const char kEncryptionProperty[] = "encryption"; | 24 const char kEncryptionProperty[] = "encryption"; |
| 25 const char kContentEncodingProperty[] = "content-encoding"; |
| 24 const char kCryptoKeyProperty[] = "crypto-key"; | 26 const char kCryptoKeyProperty[] = "crypto-key"; |
| 25 | 27 |
| 28 // Content coding name defined by ietf-httpbis-encryption-encoding. |
| 29 const char kContentCodingAes128Gcm[] = "aes128gcm"; |
| 30 |
| 26 // Directory in the GCM Store in which the encryption database will be stored. | 31 // Directory in the GCM Store in which the encryption database will be stored. |
| 27 const base::FilePath::CharType kEncryptionDirectoryName[] = | 32 const base::FilePath::CharType kEncryptionDirectoryName[] = |
| 28 FILE_PATH_LITERAL("Encryption"); | 33 FILE_PATH_LITERAL("Encryption"); |
| 29 | 34 |
| 30 } // namespace | 35 } // namespace |
| 31 | 36 |
| 32 std::string GCMEncryptionProvider::ToDecryptionResultDetailsString( | 37 std::string GCMEncryptionProvider::ToDecryptionResultDetailsString( |
| 33 DecryptionResult result) { | 38 DecryptionResult result) { |
| 34 switch (result) { | 39 switch (result) { |
| 35 case DECRYPTION_RESULT_UNENCRYPTED: | 40 case DECRYPTION_RESULT_UNENCRYPTED: |
| 36 return "Message was not encrypted"; | 41 return "Message was not encrypted"; |
| 37 case DECRYPTION_RESULT_DECRYPTED: | 42 case DECRYPTION_RESULT_DECRYPTED_DRAFT_03: |
| 38 return "Message decrypted"; | 43 return "Message decrypted (draft 03)"; |
| 44 case DECRYPTION_RESULT_DECRYPTED_DRAFT_08: |
| 45 return "Message decrypted (draft 08)"; |
| 39 case DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER: | 46 case DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER: |
| 40 return "Invalid format for the Encryption header"; | 47 return "Invalid format for the Encryption header"; |
| 41 case DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER: | 48 case DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER: |
| 42 return "Invalid format for the Crypto-Key header"; | 49 return "Invalid format for the Crypto-Key header"; |
| 43 case DECRYPTION_RESULT_NO_KEYS: | 50 case DECRYPTION_RESULT_NO_KEYS: |
| 44 return "There are no associated keys with the subscription"; | 51 return "There are no associated keys with the subscription"; |
| 45 case DECRYPTION_RESULT_INVALID_SHARED_SECRET: | 52 case DECRYPTION_RESULT_INVALID_SHARED_SECRET: |
| 46 return "The shared secret cannot be derived from the keying material"; | 53 return "The shared secret cannot be derived from the keying material"; |
| 47 case DECRYPTION_RESULT_INVALID_PAYLOAD: | 54 case DECRYPTION_RESULT_INVALID_PAYLOAD: |
| 48 return "AES-GCM decryption failed"; | 55 return "AES-GCM decryption failed"; |
| 56 case DECRYPTION_RESULT_INVALID_BINARY_HEADER: |
| 57 return "The message's binary header could not be parsed."; |
| 49 } | 58 } |
| 50 | 59 |
| 51 NOTREACHED(); | 60 NOTREACHED(); |
| 52 return "(invalid result)"; | 61 return "(invalid result)"; |
| 53 } | 62 } |
| 54 | 63 |
| 55 GCMEncryptionProvider::GCMEncryptionProvider() | 64 GCMEncryptionProvider::GCMEncryptionProvider() |
| 56 : weak_ptr_factory_(this) { | 65 : weak_ptr_factory_(this) { |
| 57 } | 66 } |
| 58 | 67 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 90 void GCMEncryptionProvider::RemoveEncryptionInfo( | 99 void GCMEncryptionProvider::RemoveEncryptionInfo( |
| 91 const std::string& app_id, | 100 const std::string& app_id, |
| 92 const std::string& authorized_entity, | 101 const std::string& authorized_entity, |
| 93 const base::Closure& callback) { | 102 const base::Closure& callback) { |
| 94 DCHECK(key_store_); | 103 DCHECK(key_store_); |
| 95 key_store_->RemoveKeys(app_id, authorized_entity, callback); | 104 key_store_->RemoveKeys(app_id, authorized_entity, callback); |
| 96 } | 105 } |
| 97 | 106 |
| 98 bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message) | 107 bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message) |
| 99 const { | 108 const { |
| 109 // Messages that explicitly specify their content coding to be "aes128gcm" |
| 110 // indicate that they use draft-ietf-webpush-encryption-08. |
| 111 auto content_encoding_iter = message.data.find(kContentEncodingProperty); |
| 112 if (content_encoding_iter != message.data.end() && |
| 113 content_encoding_iter->second == kContentCodingAes128Gcm) { |
| 114 return true; |
| 115 } |
| 116 |
| 100 // The Web Push protocol requires the encryption and crypto-key properties to | 117 // The Web Push protocol requires the encryption and crypto-key properties to |
| 101 // be set, and the raw_data field to be populated with the payload. | 118 // be set, and the raw_data field to be populated with the payload. |
| 102 if (message.data.find(kEncryptionProperty) == message.data.end() || | 119 if (message.data.find(kEncryptionProperty) == message.data.end() || |
| 103 message.data.find(kCryptoKeyProperty) == message.data.end()) | 120 message.data.find(kCryptoKeyProperty) == message.data.end()) |
| 104 return false; | 121 return false; |
| 105 | 122 |
| 106 return message.raw_data.size() > 0; | 123 return message.raw_data.size() > 0; |
| 107 } | 124 } |
| 108 | 125 |
| 109 void GCMEncryptionProvider::DecryptMessage( | 126 void GCMEncryptionProvider::DecryptMessage( |
| 110 const std::string& app_id, | 127 const std::string& app_id, |
| 111 const IncomingMessage& message, | 128 const IncomingMessage& message, |
| 112 const MessageCallback& callback) { | 129 const MessageCallback& callback) { |
| 113 DCHECK(key_store_); | 130 DCHECK(key_store_); |
| 114 if (!IsEncryptedMessage(message)) { | 131 if (!IsEncryptedMessage(message)) { |
| 115 callback.Run(DECRYPTION_RESULT_UNENCRYPTED, message); | 132 callback.Run(DECRYPTION_RESULT_UNENCRYPTED, message); |
| 116 return; | 133 return; |
| 117 } | 134 } |
| 118 | 135 |
| 119 // IsEncryptedMessage() verifies that both the Encryption and Crypto-Key HTTP | 136 std::string salt, public_key, ciphertext; |
| 120 // headers have been provided for the |message|. | 137 GCMMessageCryptographer::Version version; |
| 121 const auto& encryption_header = message.data.find(kEncryptionProperty); | 138 uint32_t record_size; |
| 122 const auto& crypto_key_header = message.data.find(kCryptoKeyProperty); | |
| 123 | 139 |
| 124 DCHECK(encryption_header != message.data.end()); | 140 auto content_encoding_iter = message.data.find(kContentEncodingProperty); |
| 125 DCHECK(crypto_key_header != message.data.end()); | 141 if (content_encoding_iter != message.data.end() && |
| 142 content_encoding_iter->second == kContentCodingAes128Gcm) { |
| 143 // The message follows encryption per draft-ietf-webpush-encryption-08. Use |
| 144 // the binary header of the message to derive the values. |
| 126 | 145 |
| 127 EncryptionHeaderIterator encryption_header_iterator( | 146 MessagePayloadParser parser(message.raw_data); |
| 128 encryption_header->second.begin(), encryption_header->second.end()); | 147 if (!parser.IsValid()) { |
| 129 if (!encryption_header_iterator.GetNext()) { | 148 DLOG(ERROR) << "Unable to parse the message's binary header"; |
| 130 DLOG(ERROR) << "Unable to parse the value of the Encryption header"; | 149 callback.Run(DECRYPTION_RESULT_INVALID_BINARY_HEADER, IncomingMessage()); |
| 131 callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER, | 150 return; |
| 132 IncomingMessage()); | 151 } |
| 133 return; | |
| 134 } | |
| 135 | 152 |
| 136 if (encryption_header_iterator.salt().size() != | 153 salt = parser.salt(); |
| 137 GCMMessageCryptographer::kSaltSize) { | 154 public_key = parser.public_key(); |
| 138 DLOG(ERROR) << "Invalid values supplied in the Encryption header"; | 155 record_size = parser.record_size(); |
| 139 callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER, | 156 ciphertext = parser.ciphertext(); |
| 140 IncomingMessage()); | 157 version = GCMMessageCryptographer::Version::DRAFT_08; |
| 141 return; | |
| 142 } | |
| 143 | 158 |
| 144 CryptoKeyHeaderIterator crypto_key_header_iterator( | 159 } else { |
| 145 crypto_key_header->second.begin(), crypto_key_header->second.end()); | 160 // The message follows encryption per draft-ietf-webpush-encryption-03. Use |
| 146 if (!crypto_key_header_iterator.GetNext()) { | 161 // the Encryption and Crypto-Key header values to derive the values. |
| 147 DLOG(ERROR) << "Unable to parse the value of the Crypto-Key header"; | |
| 148 callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER, | |
| 149 IncomingMessage()); | |
| 150 return; | |
| 151 } | |
| 152 | 162 |
| 153 // Ignore values that don't include the "dh" property. When using VAPID, it is | 163 const auto& encryption_header = message.data.find(kEncryptionProperty); |
| 154 // valid for the application server to supply multiple values. | 164 DCHECK(encryption_header != message.data.end()); |
| 155 while (crypto_key_header_iterator.dh().empty() && | |
| 156 crypto_key_header_iterator.GetNext()) {} | |
| 157 | 165 |
| 158 bool valid_crypto_key_header = false; | 166 const auto& crypto_key_header = message.data.find(kCryptoKeyProperty); |
| 159 std::string dh; | 167 DCHECK(crypto_key_header != message.data.end()); |
| 160 | 168 |
| 161 if (!crypto_key_header_iterator.dh().empty()) { | 169 EncryptionHeaderIterator encryption_header_iterator( |
| 162 dh = crypto_key_header_iterator.dh(); | 170 encryption_header->second.begin(), encryption_header->second.end()); |
| 163 valid_crypto_key_header = true; | 171 if (!encryption_header_iterator.GetNext()) { |
| 172 DLOG(ERROR) << "Unable to parse the value of the Encryption header"; |
| 173 callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER, |
| 174 IncomingMessage()); |
| 175 return; |
| 176 } |
| 164 | 177 |
| 165 // Guard against the "dh" property being included more than once. | 178 if (encryption_header_iterator.salt().size() != |
| 166 while (crypto_key_header_iterator.GetNext()) { | 179 GCMMessageCryptographer::kSaltSize) { |
| 167 if (crypto_key_header_iterator.dh().empty()) | 180 DLOG(ERROR) << "Invalid values supplied in the Encryption header"; |
| 168 continue; | 181 callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER, |
| 182 IncomingMessage()); |
| 183 return; |
| 184 } |
| 169 | 185 |
| 170 valid_crypto_key_header = false; | 186 salt = encryption_header_iterator.salt(); |
| 171 break; | 187 record_size = encryption_header_iterator.rs(); |
| 188 |
| 189 CryptoKeyHeaderIterator crypto_key_header_iterator( |
| 190 crypto_key_header->second.begin(), crypto_key_header->second.end()); |
| 191 if (!crypto_key_header_iterator.GetNext()) { |
| 192 DLOG(ERROR) << "Unable to parse the value of the Crypto-Key header"; |
| 193 callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER, |
| 194 IncomingMessage()); |
| 195 return; |
| 172 } | 196 } |
| 173 } | |
| 174 | 197 |
| 175 if (!valid_crypto_key_header) { | 198 // Ignore values that don't include the "dh" property. When using VAPID, it |
| 176 DLOG(ERROR) << "Invalid values supplied in the Crypto-Key header"; | 199 // is valid for the application server to supply multiple values. |
| 177 callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER, | 200 while (crypto_key_header_iterator.dh().empty() && |
| 178 IncomingMessage()); | 201 crypto_key_header_iterator.GetNext()) { |
| 179 return; | 202 } |
| 203 |
| 204 bool valid_crypto_key_header = false; |
| 205 |
| 206 if (!crypto_key_header_iterator.dh().empty()) { |
| 207 public_key = crypto_key_header_iterator.dh(); |
| 208 valid_crypto_key_header = true; |
| 209 |
| 210 // Guard against the "dh" property being included more than once. |
| 211 while (crypto_key_header_iterator.GetNext()) { |
| 212 if (crypto_key_header_iterator.dh().empty()) |
| 213 continue; |
| 214 |
| 215 valid_crypto_key_header = false; |
| 216 break; |
| 217 } |
| 218 } |
| 219 |
| 220 if (!valid_crypto_key_header) { |
| 221 DLOG(ERROR) << "Invalid values supplied in the Crypto-Key header"; |
| 222 callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER, |
| 223 IncomingMessage()); |
| 224 return; |
| 225 } |
| 226 |
| 227 ciphertext = message.raw_data; |
| 228 version = GCMMessageCryptographer::Version::DRAFT_03; |
| 180 } | 229 } |
| 181 | 230 |
| 182 // Use |fallback_to_empty_authorized_entity|, since this message might have | 231 // Use |fallback_to_empty_authorized_entity|, since this message might have |
| 183 // been sent to either an InstanceID token or a non-InstanceID registration. | 232 // been sent to either an InstanceID token or a non-InstanceID registration. |
| 184 key_store_->GetKeys(app_id, message.sender_id /* authorized_entity */, | 233 key_store_->GetKeys( |
| 185 true /* fallback_to_empty_authorized_entity */, | 234 app_id, message.sender_id /* authorized_entity */, |
| 186 base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey, | 235 true /* fallback_to_empty_authorized_entity */, |
| 187 weak_ptr_factory_.GetWeakPtr(), message, | 236 base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey, |
| 188 callback, encryption_header_iterator.salt(), | 237 weak_ptr_factory_.GetWeakPtr(), message.collapse_key, |
| 189 dh, encryption_header_iterator.rs())); | 238 message.sender_id, std::move(salt), std::move(public_key), |
| 239 record_size, std::move(ciphertext), version, callback)); |
| 190 } | 240 } |
| 191 | 241 |
| 192 void GCMEncryptionProvider::DidGetEncryptionInfo( | 242 void GCMEncryptionProvider::DidGetEncryptionInfo( |
| 193 const std::string& app_id, | 243 const std::string& app_id, |
| 194 const std::string& authorized_entity, | 244 const std::string& authorized_entity, |
| 195 const EncryptionInfoCallback& callback, | 245 const EncryptionInfoCallback& callback, |
| 196 const KeyPair& pair, | 246 const KeyPair& pair, |
| 197 const std::string& auth_secret) { | 247 const std::string& auth_secret) { |
| 198 if (!pair.IsInitialized()) { | 248 if (!pair.IsInitialized()) { |
| 199 key_store_->CreateKeys( | 249 key_store_->CreateKeys( |
| (...skipping 15 matching lines...) Expand all Loading... |
| 215 callback.Run(std::string() /* p256dh */, | 265 callback.Run(std::string() /* p256dh */, |
| 216 std::string() /* auth_secret */); | 266 std::string() /* auth_secret */); |
| 217 return; | 267 return; |
| 218 } | 268 } |
| 219 | 269 |
| 220 DCHECK_EQ(KeyPair::ECDH_P256, pair.type()); | 270 DCHECK_EQ(KeyPair::ECDH_P256, pair.type()); |
| 221 callback.Run(pair.public_key(), auth_secret); | 271 callback.Run(pair.public_key(), auth_secret); |
| 222 } | 272 } |
| 223 | 273 |
| 224 void GCMEncryptionProvider::DecryptMessageWithKey( | 274 void GCMEncryptionProvider::DecryptMessageWithKey( |
| 225 const IncomingMessage& message, | 275 const std::string& collapse_key, |
| 276 const std::string& sender_id, |
| 277 const std::string& salt, |
| 278 const std::string& public_key, |
| 279 uint32_t record_size, |
| 280 const std::string& ciphertext, |
| 281 GCMMessageCryptographer::Version version, |
| 226 const MessageCallback& callback, | 282 const MessageCallback& callback, |
| 227 const std::string& salt, | |
| 228 const std::string& dh, | |
| 229 uint64_t rs, | |
| 230 const KeyPair& pair, | 283 const KeyPair& pair, |
| 231 const std::string& auth_secret) { | 284 const std::string& auth_secret) { |
| 232 if (!pair.IsInitialized()) { | 285 if (!pair.IsInitialized()) { |
| 233 DLOG(ERROR) << "Unable to retrieve the keys for the incoming message."; | 286 DLOG(ERROR) << "Unable to retrieve the keys for the incoming message."; |
| 234 callback.Run(DECRYPTION_RESULT_NO_KEYS, IncomingMessage()); | 287 callback.Run(DECRYPTION_RESULT_NO_KEYS, IncomingMessage()); |
| 235 return; | 288 return; |
| 236 } | 289 } |
| 237 | 290 |
| 238 DCHECK_EQ(KeyPair::ECDH_P256, pair.type()); | 291 DCHECK_EQ(KeyPair::ECDH_P256, pair.type()); |
| 239 | 292 |
| 240 std::string shared_secret; | 293 std::string shared_secret; |
| 241 if (!ComputeSharedP256Secret(pair.private_key(), pair.public_key_x509(), dh, | 294 if (!ComputeSharedP256Secret(pair.private_key(), pair.public_key_x509(), |
| 242 &shared_secret)) { | 295 public_key, &shared_secret)) { |
| 243 DLOG(ERROR) << "Unable to calculate the shared secret."; | 296 DLOG(ERROR) << "Unable to calculate the shared secret."; |
| 244 callback.Run(DECRYPTION_RESULT_INVALID_SHARED_SECRET, IncomingMessage()); | 297 callback.Run(DECRYPTION_RESULT_INVALID_SHARED_SECRET, IncomingMessage()); |
| 245 return; | 298 return; |
| 246 } | 299 } |
| 247 | 300 |
| 248 std::string plaintext; | 301 std::string plaintext; |
| 249 | 302 |
| 250 GCMMessageCryptographer cryptographer( | 303 GCMMessageCryptographer cryptographer(version); |
| 251 GCMMessageCryptographer::Version::DRAFT_03); | |
| 252 | 304 |
| 253 if (!cryptographer.Decrypt(pair.public_key(), dh, shared_secret, auth_secret, | 305 if (!cryptographer.Decrypt(pair.public_key(), public_key, shared_secret, |
| 254 salt, message.raw_data, rs, &plaintext)) { | 306 auth_secret, salt, ciphertext, record_size, |
| 307 &plaintext)) { |
| 255 DLOG(ERROR) << "Unable to decrypt the incoming data."; | 308 DLOG(ERROR) << "Unable to decrypt the incoming data."; |
| 256 callback.Run(DECRYPTION_RESULT_INVALID_PAYLOAD, IncomingMessage()); | 309 callback.Run(DECRYPTION_RESULT_INVALID_PAYLOAD, IncomingMessage()); |
| 257 return; | 310 return; |
| 258 } | 311 } |
| 259 | 312 |
| 260 IncomingMessage decrypted_message; | 313 IncomingMessage decrypted_message; |
| 261 decrypted_message.collapse_key = message.collapse_key; | 314 decrypted_message.collapse_key = collapse_key; |
| 262 decrypted_message.sender_id = message.sender_id; | 315 decrypted_message.sender_id = sender_id; |
| 263 decrypted_message.raw_data.swap(plaintext); | 316 decrypted_message.raw_data.swap(plaintext); |
| 264 decrypted_message.decrypted = true; | 317 decrypted_message.decrypted = true; |
| 265 | 318 |
| 266 // There must be no data associated with the decrypted message at this point, | 319 // There must be no data associated with the decrypted message at this point, |
| 267 // to make sure that we don't end up in an infinite decryption loop. | 320 // to make sure that we don't end up in an infinite decryption loop. |
| 268 DCHECK_EQ(0u, decrypted_message.data.size()); | 321 DCHECK_EQ(0u, decrypted_message.data.size()); |
| 269 | 322 |
| 270 callback.Run(DECRYPTION_RESULT_DECRYPTED, decrypted_message); | 323 callback.Run(version == GCMMessageCryptographer::Version::DRAFT_03 |
| 324 ? DECRYPTION_RESULT_DECRYPTED_DRAFT_03 |
| 325 : DECRYPTION_RESULT_DECRYPTED_DRAFT_08, |
| 326 decrypted_message); |
| 271 } | 327 } |
| 272 | 328 |
| 273 } // namespace gcm | 329 } // namespace gcm |
| OLD | NEW |