blob: fa46cbae72e2111b6302e66f7a7a544efd7e2b1d [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gcm_driver/crypto/gcm_encryption_provider.h"
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/logging.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/p256_key_util.h"
#include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h"
namespace gcm {
namespace {
const char kEncryptionProperty[] = "encryption";
const char kCryptoKeyProperty[] = "crypto-key";
// Directory in the GCM Store in which the encryption database will be stored.
const base::FilePath::CharType kEncryptionDirectoryName[] =
FILE_PATH_LITERAL("Encryption");
} // namespace
std::string GCMEncryptionProvider::ToDecryptionResultDetailsString(
DecryptionResult result) {
switch (result) {
case DECRYPTION_RESULT_UNENCRYPTED:
return "Message was not encrypted";
case DECRYPTION_RESULT_DECRYPTED:
return "Message decrypted";
case DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER:
return "Invalid format for the Encryption header";
case DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER:
return "Invalid format for the Crypto-Key header";
case DECRYPTION_RESULT_NO_KEYS:
return "There are no associated keys with the subscription";
case DECRYPTION_RESULT_INVALID_SHARED_SECRET:
return "The shared secret cannot be derived from the keying material";
case DECRYPTION_RESULT_INVALID_PAYLOAD:
return "AES-GCM decryption failed";
}
NOTREACHED();
return "(invalid result)";
}
GCMEncryptionProvider::GCMEncryptionProvider()
: weak_ptr_factory_(this) {
}
GCMEncryptionProvider::~GCMEncryptionProvider() {
}
void GCMEncryptionProvider::Init(
const base::FilePath& store_path,
const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) {
DCHECK(!key_store_);
base::FilePath encryption_store_path = store_path;
// |store_path| can be empty in tests, which means that the database should
// be created in memory rather than on-disk.
if (!store_path.empty())
encryption_store_path = store_path.Append(kEncryptionDirectoryName);
key_store_.reset(
new GCMKeyStore(encryption_store_path, blocking_task_runner));
}
void GCMEncryptionProvider::GetEncryptionInfo(
const std::string& app_id,
const std::string& authorized_entity,
const EncryptionInfoCallback& callback) {
DCHECK(key_store_);
key_store_->GetKeys(app_id, authorized_entity,
false /* fallback_to_empty_authorized_entity */,
base::Bind(&GCMEncryptionProvider::DidGetEncryptionInfo,
weak_ptr_factory_.GetWeakPtr(), app_id,
authorized_entity, callback));
}
void GCMEncryptionProvider::RemoveEncryptionInfo(
const std::string& app_id,
const std::string& authorized_entity,
const base::Closure& callback) {
DCHECK(key_store_);
key_store_->RemoveKeys(app_id, authorized_entity, callback);
}
bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message)
const {
// The Web Push protocol requires the encryption and crypto-key properties to
// be set, and the raw_data field to be populated with the payload.
if (message.data.find(kEncryptionProperty) == message.data.end() ||
message.data.find(kCryptoKeyProperty) == message.data.end())
return false;
return message.raw_data.size() > 0;
}
void GCMEncryptionProvider::DecryptMessage(
const std::string& app_id,
const IncomingMessage& message,
const MessageCallback& callback) {
DCHECK(key_store_);
if (!IsEncryptedMessage(message)) {
callback.Run(DECRYPTION_RESULT_UNENCRYPTED, message);
return;
}
// IsEncryptedMessage() verifies that both the Encryption and Crypto-Key HTTP
// headers have been provided for the |message|.
const auto& encryption_header = message.data.find(kEncryptionProperty);
const auto& crypto_key_header = message.data.find(kCryptoKeyProperty);
DCHECK(encryption_header != message.data.end());
DCHECK(crypto_key_header != message.data.end());
EncryptionHeaderIterator encryption_header_iterator(
encryption_header->second.begin(), encryption_header->second.end());
if (!encryption_header_iterator.GetNext()) {
DLOG(ERROR) << "Unable to parse the value of the Encryption header";
callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER,
IncomingMessage());
return;
}
if (encryption_header_iterator.salt().size() !=
GCMMessageCryptographer::kSaltSize) {
DLOG(ERROR) << "Invalid values supplied in the Encryption header";
callback.Run(DECRYPTION_RESULT_INVALID_ENCRYPTION_HEADER,
IncomingMessage());
return;
}
CryptoKeyHeaderIterator crypto_key_header_iterator(
crypto_key_header->second.begin(), crypto_key_header->second.end());
if (!crypto_key_header_iterator.GetNext()) {
DLOG(ERROR) << "Unable to parse the value of the Crypto-Key header";
callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER,
IncomingMessage());
return;
}
// Ignore values that don't include the "dh" property. When using VAPID, it is
// valid for the application server to supply multiple values.
while (crypto_key_header_iterator.dh().empty() &&
crypto_key_header_iterator.GetNext()) {}
bool valid_crypto_key_header = false;
std::string dh;
if (!crypto_key_header_iterator.dh().empty()) {
dh = crypto_key_header_iterator.dh();
valid_crypto_key_header = true;
// Guard against the "dh" property being included more than once.
while (crypto_key_header_iterator.GetNext()) {
if (crypto_key_header_iterator.dh().empty())
continue;
valid_crypto_key_header = false;
break;
}
}
if (!valid_crypto_key_header) {
DLOG(ERROR) << "Invalid values supplied in the Crypto-Key header";
callback.Run(DECRYPTION_RESULT_INVALID_CRYPTO_KEY_HEADER,
IncomingMessage());
return;
}
// Use |fallback_to_empty_authorized_entity|, since this message might have
// been sent to either an InstanceID token or a non-InstanceID registration.
key_store_->GetKeys(app_id, message.sender_id /* authorized_entity */,
true /* fallback_to_empty_authorized_entity */,
base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey,
weak_ptr_factory_.GetWeakPtr(), message,
callback, encryption_header_iterator.salt(),
dh, encryption_header_iterator.rs()));
}
void GCMEncryptionProvider::DidGetEncryptionInfo(
const std::string& app_id,
const std::string& authorized_entity,
const EncryptionInfoCallback& callback,
const KeyPair& pair,
const std::string& auth_secret) {
if (!pair.IsInitialized()) {
key_store_->CreateKeys(
app_id, authorized_entity,
base::Bind(&GCMEncryptionProvider::DidCreateEncryptionInfo,
weak_ptr_factory_.GetWeakPtr(), callback));
return;
}
DCHECK_EQ(KeyPair::ECDH_P256, pair.type());
callback.Run(pair.public_key(), auth_secret);
}
void GCMEncryptionProvider::DidCreateEncryptionInfo(
const EncryptionInfoCallback& callback,
const KeyPair& pair,
const std::string& auth_secret) {
if (!pair.IsInitialized()) {
callback.Run(std::string() /* p256dh */,
std::string() /* auth_secret */);
return;
}
DCHECK_EQ(KeyPair::ECDH_P256, pair.type());
callback.Run(pair.public_key(), auth_secret);
}
void GCMEncryptionProvider::DecryptMessageWithKey(
const IncomingMessage& message,
const MessageCallback& callback,
const std::string& salt,
const std::string& dh,
uint64_t rs,
const KeyPair& pair,
const std::string& auth_secret) {
if (!pair.IsInitialized()) {
DLOG(ERROR) << "Unable to retrieve the keys for the incoming message.";
callback.Run(DECRYPTION_RESULT_NO_KEYS, IncomingMessage());
return;
}
DCHECK_EQ(KeyPair::ECDH_P256, pair.type());
std::string shared_secret;
if (!ComputeSharedP256Secret(pair.private_key(), pair.public_key_x509(), dh,
&shared_secret)) {
DLOG(ERROR) << "Unable to calculate the shared secret.";
callback.Run(DECRYPTION_RESULT_INVALID_SHARED_SECRET, IncomingMessage());
return;
}
std::string plaintext;
GCMMessageCryptographer cryptographer(pair.public_key(), dh, auth_secret);
if (!cryptographer.Decrypt(message.raw_data, shared_secret, salt, rs,
&plaintext)) {
DLOG(ERROR) << "Unable to decrypt the incoming data.";
callback.Run(DECRYPTION_RESULT_INVALID_PAYLOAD, IncomingMessage());
return;
}
IncomingMessage decrypted_message;
decrypted_message.collapse_key = message.collapse_key;
decrypted_message.sender_id = message.sender_id;
decrypted_message.raw_data.swap(plaintext);
decrypted_message.decrypted = true;
// There must be no data associated with the decrypted message at this point,
// to make sure that we don't end up in an infinite decryption loop.
DCHECK_EQ(0u, decrypted_message.data.size());
callback.Run(DECRYPTION_RESULT_DECRYPTED, decrypted_message);
}
} // namespace gcm