// Copyright 2014 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/gcm_driver/gcm_driver.h" #include #include #include "base/bind.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "components/gcm_driver/crypto/gcm_decryption_result.h" #include "components/gcm_driver/crypto/gcm_encryption_result.h" #include "components/gcm_driver/gcm_app_handler.h" namespace gcm { InstanceIDHandler::InstanceIDHandler() = default; InstanceIDHandler::~InstanceIDHandler() = default; void InstanceIDHandler::DeleteAllTokensForApp(const std::string& app_id, DeleteTokenCallback callback) { DeleteToken(app_id, "*", "*", std::move(callback)); } GCMDriver::GCMDriver( const base::FilePath& store_path, const scoped_refptr& blocking_task_runner) { // The |blocking_task_runner| can be nullptr for tests that do not need the // encryption capabilities of the GCMDriver class. if (blocking_task_runner) encryption_provider_.Init(store_path, blocking_task_runner); } GCMDriver::~GCMDriver() = default; void GCMDriver::Register(const std::string& app_id, const std::vector& sender_ids, RegisterCallback callback) { DCHECK(!app_id.empty()); DCHECK(!sender_ids.empty() && sender_ids.size() <= kMaxSenders); DCHECK(!callback.is_null()); GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); if (result != GCMClient::SUCCESS) { std::move(callback).Run(std::string(), result); return; } // If previous register operation is still in progress, bail out. if (register_callbacks_.find(app_id) != register_callbacks_.end()) { std::move(callback).Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING); return; } // Normalize the sender IDs by making them sorted. std::vector normalized_sender_ids = sender_ids; std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end()); register_callbacks_[app_id] = std::move(callback); // If previous unregister operation is still in progress, wait until it // finishes. We don't want to throw ASYNC_OPERATION_PENDING when the user // uninstalls an app (ungistering) and then reinstalls the app again // (registering). auto unregister_iter = unregister_callbacks_.find(app_id); if (unregister_iter != unregister_callbacks_.end()) { // Replace the original unregister callback with an intermediate callback // that will invoke the original unregister callback and trigger the pending // registration after the unregistration finishes. // Note that some parameters to RegisterAfterUnregister are specified here // when the callback is created (base::Bind supports the partial binding // of parameters). unregister_iter->second = base::BindOnce( &GCMDriver::RegisterAfterUnregister, weak_ptr_factory_.GetWeakPtr(), app_id, normalized_sender_ids, std::move(unregister_iter->second)); return; } RegisterImpl(app_id, normalized_sender_ids); } void GCMDriver::Unregister(const std::string& app_id, UnregisterCallback callback) { UnregisterInternal(app_id, nullptr /* sender_id */, std::move(callback)); } void GCMDriver::UnregisterWithSenderId(const std::string& app_id, const std::string& sender_id, UnregisterCallback callback) { DCHECK(!sender_id.empty()); UnregisterInternal(app_id, &sender_id, std::move(callback)); } void GCMDriver::UnregisterInternal(const std::string& app_id, const std::string* sender_id, UnregisterCallback callback) { DCHECK(!app_id.empty()); DCHECK(!callback.is_null()); GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); if (result != GCMClient::SUCCESS) { std::move(callback).Run(result); return; } // If previous un/register operation is still in progress, bail out. if (register_callbacks_.find(app_id) != register_callbacks_.end() || unregister_callbacks_.find(app_id) != unregister_callbacks_.end()) { std::move(callback).Run(GCMClient::ASYNC_OPERATION_PENDING); return; } unregister_callbacks_[app_id] = std::move(callback); if (sender_id) UnregisterWithSenderIdImpl(app_id, *sender_id); else UnregisterImpl(app_id); } void GCMDriver::Send(const std::string& app_id, const std::string& receiver_id, const OutgoingMessage& message, SendCallback callback) { DCHECK(!app_id.empty()); DCHECK(!receiver_id.empty()); DCHECK(!callback.is_null()); GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); if (result != GCMClient::SUCCESS) { std::move(callback).Run(std::string(), result); return; } // If the message with send ID is still in progress, bail out. std::pair key(app_id, message.id); if (send_callbacks_.find(key) != send_callbacks_.end()) { std::move(callback).Run(message.id, GCMClient::INVALID_PARAMETER); return; } send_callbacks_[key] = std::move(callback); SendImpl(app_id, receiver_id, message); } void GCMDriver::GetEncryptionInfo(const std::string& app_id, GetEncryptionInfoCallback callback) { encryption_provider_.GetEncryptionInfo(app_id, "" /* authorized_entity */, std::move(callback)); } void GCMDriver::UnregisterWithSenderIdImpl(const std::string& app_id, const std::string& sender_id) { NOTREACHED(); } void GCMDriver::RegisterFinished(const std::string& app_id, const std::string& registration_id, GCMClient::Result result) { auto callback_iter = register_callbacks_.find(app_id); if (callback_iter == register_callbacks_.end()) { // The callback could have been removed when the app is uninstalled. return; } RegisterCallback callback = std::move(callback_iter->second); register_callbacks_.erase(callback_iter); std::move(callback).Run(registration_id, result); } void GCMDriver::RemoveEncryptionInfoAfterUnregister(const std::string& app_id, GCMClient::Result result) { encryption_provider_.RemoveEncryptionInfo( app_id, "" /* authorized_entity */, base::BindOnce(&GCMDriver::UnregisterFinished, weak_ptr_factory_.GetWeakPtr(), app_id, result)); } void GCMDriver::UnregisterFinished(const std::string& app_id, GCMClient::Result result) { auto callback_iter = unregister_callbacks_.find(app_id); if (callback_iter == unregister_callbacks_.end()) return; UnregisterCallback callback = std::move(callback_iter->second); unregister_callbacks_.erase(callback_iter); std::move(callback).Run(result); } void GCMDriver::SendFinished(const std::string& app_id, const std::string& message_id, GCMClient::Result result) { auto callback_iter = send_callbacks_.find( std::pair(app_id, message_id)); if (callback_iter == send_callbacks_.end()) { // The callback could have been removed when the app is uninstalled. return; } SendCallback callback = std::move(callback_iter->second); send_callbacks_.erase(callback_iter); std::move(callback).Run(message_id, result); } void GCMDriver::Shutdown() { for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin(); iter != app_handlers_.end(); ++iter) { DVLOG(1) << "Calling ShutdownHandler for: " << iter->first; iter->second->ShutdownHandler(); } app_handlers_.clear(); } void GCMDriver::AddAppHandler(const std::string& app_id, GCMAppHandler* handler) { DCHECK(!app_id.empty()); DCHECK(handler); DCHECK_EQ(app_handlers_.count(app_id), 0u); app_handlers_[app_id] = handler; DVLOG(1) << "App handler added for: " << app_id; } void GCMDriver::RemoveAppHandler(const std::string& app_id) { DCHECK(!app_id.empty()); app_handlers_.erase(app_id); DVLOG(1) << "App handler removed for: " << app_id; } GCMAppHandler* GCMDriver::GetAppHandler(const std::string& app_id) { // Look for exact match. GCMAppHandlerMap::const_iterator iter = app_handlers_.find(app_id); if (iter != app_handlers_.end()) return iter->second; // Ask the handlers whether they know how to handle it. for (iter = app_handlers_.begin(); iter != app_handlers_.end(); ++iter) { if (iter->second->CanHandle(app_id)) return iter->second; } return nullptr; } GCMEncryptionProvider* GCMDriver::GetEncryptionProviderInternal() { return &encryption_provider_; } bool GCMDriver::HasRegisterCallback(const std::string& app_id) { return register_callbacks_.find(app_id) != register_callbacks_.end(); } void GCMDriver::ClearCallbacks() { register_callbacks_.clear(); unregister_callbacks_.clear(); send_callbacks_.clear(); } void GCMDriver::DispatchMessage(const std::string& app_id, const IncomingMessage& message) { encryption_provider_.DecryptMessage( app_id, message, base::BindOnce(&GCMDriver::DispatchMessageInternal, weak_ptr_factory_.GetWeakPtr(), app_id)); } void GCMDriver::DispatchMessageInternal(const std::string& app_id, GCMDecryptionResult result, IncomingMessage message) { UMA_HISTOGRAM_ENUMERATION("GCM.Crypto.DecryptMessageResult", result, GCMDecryptionResult::ENUM_SIZE); switch (result) { case GCMDecryptionResult::UNENCRYPTED: case GCMDecryptionResult::DECRYPTED_DRAFT_03: case GCMDecryptionResult::DECRYPTED_DRAFT_08: { GCMAppHandler* handler = GetAppHandler(app_id); UMA_HISTOGRAM_BOOLEAN("GCM.DeliveredToAppHandler", !!handler); if (handler) handler->OnMessage(app_id, message); // TODO(peter/harkness): Surface unavailable app handlers on // chrome://gcm-internals and send a delivery receipt. return; } case GCMDecryptionResult::INVALID_ENCRYPTION_HEADER: case GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER: case GCMDecryptionResult::NO_KEYS: case GCMDecryptionResult::INVALID_SHARED_SECRET: case GCMDecryptionResult::INVALID_PAYLOAD: case GCMDecryptionResult::INVALID_BINARY_HEADER_PAYLOAD_LENGTH: case GCMDecryptionResult::INVALID_BINARY_HEADER_RECORD_SIZE: case GCMDecryptionResult::INVALID_BINARY_HEADER_PUBLIC_KEY_LENGTH: case GCMDecryptionResult::INVALID_BINARY_HEADER_PUBLIC_KEY_FORMAT: { RecordDecryptionFailure(app_id, result); GCMAppHandler* handler = GetAppHandler(app_id); if (handler) { handler->OnMessageDecryptionFailed( app_id, message.message_id, ToGCMDecryptionResultDetailsString(result)); } return; } case GCMDecryptionResult::ENUM_SIZE: break; // deliberate fall-through } NOTREACHED(); } void GCMDriver::RegisterAfterUnregister( const std::string& app_id, const std::vector& normalized_sender_ids, UnregisterCallback unregister_callback, GCMClient::Result result) { // Invoke the original unregister callback. std::move(unregister_callback).Run(result); // Trigger the pending registration. DCHECK(register_callbacks_.find(app_id) != register_callbacks_.end()); RegisterImpl(app_id, normalized_sender_ids); } void GCMDriver::EncryptMessage(const std::string& app_id, const std::string& authorized_entity, const std::string& p256dh, const std::string& auth_secret, const std::string& message, EncryptMessageCallback callback) { encryption_provider_.EncryptMessage( app_id, authorized_entity, p256dh, auth_secret, message, base::BindOnce(&GCMDriver::OnMessageEncrypted, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void GCMDriver::OnMessageEncrypted(EncryptMessageCallback callback, GCMEncryptionResult result, std::string message) { UMA_HISTOGRAM_ENUMERATION("GCM.Crypto.EncryptMessageResult", result, GCMEncryptionResult::ENUM_SIZE); std::move(callback).Run(result, std::move(message)); } void GCMDriver::DecryptMessage(const std::string& app_id, const std::string& authorized_entity, const std::string& message, DecryptMessageCallback callback) { IncomingMessage incoming_message; incoming_message.sender_id = authorized_entity; incoming_message.raw_data = message; incoming_message.data[GCMEncryptionProvider::kContentEncodingProperty] = GCMEncryptionProvider::kContentCodingAes128Gcm; encryption_provider_.DecryptMessage( app_id, incoming_message, base::BindOnce(&GCMDriver::OnMessageDecrypted, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void GCMDriver::OnMessageDecrypted(DecryptMessageCallback callback, GCMDecryptionResult result, IncomingMessage message) { UMA_HISTOGRAM_ENUMERATION("GCM.Crypto.DecryptMessageResult", result, GCMDecryptionResult::ENUM_SIZE); std::move(callback).Run(result, std::move(message.raw_data)); } } // namespace gcm