| Index: components/gcm_driver/crypto/gcm_encryption_provider.cc
|
| diff --git a/components/gcm_driver/crypto/gcm_encryption_provider.cc b/components/gcm_driver/crypto/gcm_encryption_provider.cc
|
| index 52ee156133d35a810208dd0e0f6fabacd50a5512..f77ff95069bf6d77f709e7e51973a14f1f85d373 100644
|
| --- a/components/gcm_driver/crypto/gcm_encryption_provider.cc
|
| +++ b/components/gcm_driver/crypto/gcm_encryption_provider.cc
|
| @@ -4,13 +4,44 @@
|
|
|
| #include "components/gcm_driver/crypto/gcm_encryption_provider.h"
|
|
|
| +#include "base/base64.h"
|
| #include "base/bind.h"
|
| #include "base/logging.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "components/gcm_driver/common/gcm_messages.h"
|
| +#include "components/gcm_driver/crypto/encryption_header_parsers.h"
|
| #include "components/gcm_driver/crypto/gcm_key_store.h"
|
| +#include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
|
| #include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h"
|
| +#include "crypto/curve25519.h"
|
|
|
| namespace gcm {
|
|
|
| +namespace {
|
| +
|
| +// TODO(peter): Generalize a URL-safe base64 implementation.
|
| +bool Base64DecodeUrlSafe(const std::string& input, std::string* output) {
|
| + if (input.find_first_of("+/=") != std::string::npos)
|
| + return false;
|
| +
|
| + // Add padding.
|
| + size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
|
| + std::string padded_input(input);
|
| + padded_input.resize(padded_size, '=');
|
| +
|
| + // Convert to standard base64 alphabet.
|
| + base::ReplaceChars(padded_input, "-", "+", &padded_input);
|
| + base::ReplaceChars(padded_input, "_", "/", &padded_input);
|
| +
|
| + return base::Base64Decode(padded_input, output);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +const char kEncryptionProperty[] = "encryption";
|
| +const char kEncryptionKeyProperty[] = "encryption_key";
|
| +const char kEncryptionDataProperty[] = "encryption_data";
|
| +
|
| GCMEncryptionProvider::GCMEncryptionProvider()
|
| : weak_ptr_factory_(this) {
|
| }
|
| @@ -59,4 +90,127 @@ void GCMEncryptionProvider::DidCreatePublicKey(
|
| callback.Run(pair.public_key());
|
| }
|
|
|
| +bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message)
|
| + const {
|
| + // Both the proprietary GCM protocol and the Web Push protocol require the
|
| + // encryption and encryption_key properties to be set.
|
| + if (message.data.find(kEncryptionProperty) == message.data.end() ||
|
| + message.data.find(kEncryptionKeyProperty) == message.data.end())
|
| + return false;
|
| +
|
| + // The proprietary GCM protocol will store the encrypted payload in an app
|
| + // data field called "encryption_data".
|
| + if (message.data.find(kEncryptionDataProperty) != message.data.end())
|
| + return true;
|
| +
|
| + // The Web Push protocol uses the raw message data for the encrypted payload.
|
| + return message.raw_data.size() > 0;
|
| +}
|
| +
|
| +void GCMEncryptionProvider::DecryptMessage(const std::string& app_id,
|
| + const IncomingMessage& message,
|
| + const MessageCallback& callback) {
|
| + DCHECK(key_store_);
|
| +
|
| + const auto& encryption_header = message.data.find(kEncryptionProperty);
|
| + const auto& encryption_key_header = message.data.find(kEncryptionKeyProperty);
|
| +
|
| + // Callers are expected to call IsEncryptedMessage() prior to this method.
|
| + DCHECK(encryption_header != message.data.end());
|
| + DCHECK(encryption_key_header != message.data.end());
|
| +
|
| + std::vector<EncryptionHeaderValue> encryption_headers;
|
| + if (!ParseEncryptionHeader(encryption_header->second, &encryption_headers)) {
|
| + DLOG(ERROR) << "Unable to parse the value of the Encryption header";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| +
|
| + std::vector<EncryptionKeyHeaderValue> encryption_key_headers;
|
| + if (!ParseEncryptionKeyHeader(encryption_key_header->second,
|
| + &encryption_key_headers)) {
|
| + DLOG(ERROR) << "Unable to parse the value of the Encryption-Key header";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| +
|
| + // Note: The HTTP Encrypted Content specification defines that multiple rounds
|
| + // of encryption are allowed. Chrome's Web Push protocol implementation does
|
| + // not currently support this, due to not using the "keyid" parameter.
|
| + if (encryption_headers.size() != 1 || encryption_key_headers.size() != 1) {
|
| + DLOG(ERROR) << "Only a single encryption key is currently supported";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| +
|
| + std::string ciphertext;
|
| +
|
| + // Determine the payload. For the proprietary GCM protocol the payload is part
|
| + // of the app data, base64 URL encoded. For the Web Push protocol, it's
|
| + // included as raw data for the incoming message.
|
| + const auto& encryption_data_iter = message.data.find(kEncryptionDataProperty);
|
| + if (encryption_data_iter != message.data.end()) {
|
| + if (!Base64DecodeUrlSafe(encryption_data_iter->second, &ciphertext)) {
|
| + DLOG(ERROR) << "Unable to URL-safe base64 decode the encrypted data";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| + } else {
|
| + DCHECK(message.raw_data.size());
|
| + ciphertext = message.raw_data;
|
| + }
|
| +
|
| + key_store_->GetKeys(
|
| + app_id, base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey,
|
| + weak_ptr_factory_.GetWeakPtr(), message, callback,
|
| + encryption_headers[0], encryption_key_headers[0],
|
| + ciphertext));
|
| +}
|
| +
|
| +void GCMEncryptionProvider::DecryptMessageWithKey(
|
| + const IncomingMessage& message,
|
| + const MessageCallback& callback,
|
| + const EncryptionHeaderValue& encryption_header,
|
| + const EncryptionKeyHeaderValue& encryption_key_header,
|
| + const std::string& ciphertext,
|
| + const KeyPair& pair) {
|
| + if (!pair.IsInitialized()) {
|
| + DLOG(ERROR) << "Unable to retrieve the keys for the incoming message.";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| +
|
| + DCHECK_EQ(KeyPair::ECDH_CURVE_25519, pair.type());
|
| +
|
| + // Calculate the shared secret between the client's private key and the
|
| + // server's public key.
|
| + uint8_t shared_key[crypto::curve25519::kBytes];
|
| + crypto::curve25519::ScalarMult(
|
| + reinterpret_cast<const unsigned char*>(pair.private_key().data()),
|
| + reinterpret_cast<const unsigned char*>(encryption_key_header.dh.data()),
|
| + shared_key);
|
| +
|
| + const std::string shared_key_string(shared_key,
|
| + shared_key + sizeof(shared_key));
|
| +
|
| + std::string plaintext;
|
| +
|
| + // TODO(peter): Support explicit keys for the decryption (don't use HKDF).
|
| +
|
| + GCMMessageCryptographer cryptographer;
|
| + if (!cryptographer.Decrypt(ciphertext, shared_key_string,
|
| + encryption_header.salt, encryption_header.rs,
|
| + &plaintext)) {
|
| + DLOG(ERROR) << "Unable to decrypt the incoming data.";
|
| + callback.Run(IncomingMessage(), false /* success */);
|
| + return;
|
| + }
|
| +
|
| + IncomingMessage decrypted_message;
|
| + decrypted_message.collapse_key = message.collapse_key;
|
| + decrypted_message.raw_data = plaintext;
|
| +
|
| + callback.Run(decrypted_message, true /* success */);
|
| +}
|
| +
|
| } // namespace gcm
|
|
|