// Copyright 2015 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/crypto/gcm_encryption_provider.h"

#include <memory>

#include "base/base64.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_view_util.h"
#include "base/task/sequenced_task_runner.h"
#include "components/gcm_driver/common/gcm_message.h"
#include "components/gcm_driver/crypto/encryption_header_parsers.h"
#include "components/gcm_driver/crypto/gcm_decryption_result.h"
#include "components/gcm_driver/crypto/gcm_encryption_result.h"
#include "components/gcm_driver/crypto/gcm_key_store.h"
#include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
#include "components/gcm_driver/crypto/message_payload_parser.h"
#include "components/gcm_driver/crypto/p256_key_util.h"
#include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h"
#include "components/gcm_driver/crypto/rfc8291_util.h"
#include "crypto/keypair.h"
#include "crypto/random.h"

namespace gcm {

namespace {

const char kEncryptionProperty[] = "encryption";
const char kCryptoKeyProperty[] = "crypto-key";
const char kInternalRawData[] = "_googRawData";

// Directory in the GCM Store in which the encryption database will be stored.
const base::FilePath::CharType kEncryptionDirectoryName[] =
    FILE_PATH_LITERAL("Encryption");

IncomingMessage CreateMessageWithId(const std::string& message_id) {
  IncomingMessage message;
  message.message_id = message_id;
  return message;
}

}  // namespace

GCMEncryptionProvider::GCMEncryptionProvider() = default;

GCMEncryptionProvider::~GCMEncryptionProvider() = default;

// static
const char GCMEncryptionProvider::kContentEncodingProperty[] =
    "content-encoding";

// static
const char GCMEncryptionProvider::kContentCodingAes128Gcm[] = "aes128gcm";

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_ = std::make_unique<GCMKeyStore>(encryption_store_path,
                                             blocking_task_runner);
}

void GCMEncryptionProvider::GetEncryptionInfo(
    const std::string& app_id,
    const std::string& authorized_entity,
    EncryptionInfoCallback callback) {
  DCHECK(key_store_);
  key_store_->GetKeys(
      app_id, authorized_entity,
      false /* fallback_to_empty_authorized_entity */,
      base::BindOnce(&GCMEncryptionProvider::DidGetEncryptionInfo,
                     weak_ptr_factory_.GetWeakPtr(), app_id, authorized_entity,
                     std::move(callback)));
}

void GCMEncryptionProvider::DidGetEncryptionInfo(
    const std::string& app_id,
    const std::string& authorized_entity,
    EncryptionInfoCallback callback,
    std::optional<crypto::keypair::PrivateKey> key,
    const std::string& auth_secret) {
  if (!key) {
    key_store_->CreateKeys(
        app_id, authorized_entity,
        base::BindOnce(&GCMEncryptionProvider::DidCreateEncryptionInfo,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
    return;
  }

  std::string uncompressed(
      base::as_string_view(key->ToUncompressedX962Point()));
  std::move(callback).Run(std::move(uncompressed), auth_secret);
}

void GCMEncryptionProvider::RemoveEncryptionInfo(
    const std::string& app_id,
    const std::string& authorized_entity,
    base::OnceClosure callback) {
  DCHECK(key_store_);
  key_store_->RemoveKeys(app_id, authorized_entity, std::move(callback));
}

bool GCMEncryptionProvider::IsEncryptedMessage(
    const IncomingMessage& message) const {
  // Messages that explicitly specify their content coding to be "aes128gcm"
  // indicate that they use draft-ietf-webpush-encryption-08.
  auto content_encoding_iter = message.data.find(kContentEncodingProperty);
  if (content_encoding_iter != message.data.end() &&
      content_encoding_iter->second == kContentCodingAes128Gcm) {
    return true;
  }

  // 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,
                                           DecryptMessageCallback callback) {
  DCHECK(key_store_);
  if (!IsEncryptedMessage(message)) {
    std::move(callback).Run(GCMDecryptionResult::UNENCRYPTED, message);
    return;
  }

  std::string salt, public_key, ciphertext;
  GCMMessageCryptographer::Version version;
  uint32_t record_size;

  auto content_encoding_iter = message.data.find(kContentEncodingProperty);
  if (content_encoding_iter != message.data.end() &&
      content_encoding_iter->second == kContentCodingAes128Gcm) {
    // The message follows encryption per draft-ietf-webpush-encryption-08. Use
    // the binary header of the message to derive the values.

    auto parser = std::make_unique<MessagePayloadParser>(message.raw_data);
    if (!parser->IsValid()) {
      // Attempt to parse base64 encoded internal raw data.
      auto raw_data_iter = message.data.find(kInternalRawData);
      std::string raw_data;
      if (raw_data_iter == message.data.end() ||
          !base::Base64Decode(raw_data_iter->second, &raw_data) ||
          !(parser = std::make_unique<MessagePayloadParser>(raw_data))
               ->IsValid()) {
        DLOG(ERROR) << "Unable to parse the message's binary header";
        std::move(callback).Run(parser->GetFailureReason(),
                                CreateMessageWithId(message.message_id));
        return;
      }
    }

    salt = parser->salt();
    public_key = parser->public_key();
    record_size = parser->record_size();
    ciphertext = parser->ciphertext();
    version = GCMMessageCryptographer::Version::DRAFT_08;
  } else {
    // The message follows encryption per draft-ietf-webpush-encryption-03. Use
    // the Encryption and Crypto-Key header values to derive the values.

    const auto& encryption_header = message.data.find(kEncryptionProperty);
    CHECK(encryption_header != message.data.end());

    const auto& crypto_key_header = message.data.find(kCryptoKeyProperty);
    CHECK(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";
      std::move(callback).Run(GCMDecryptionResult::INVALID_ENCRYPTION_HEADER,
                              CreateMessageWithId(message.message_id));
      return;
    }

    if (encryption_header_iterator.salt().size() !=
        GCMMessageCryptographer::kSaltSize) {
      DLOG(ERROR) << "Invalid values supplied in the Encryption header";
      std::move(callback).Run(GCMDecryptionResult::INVALID_ENCRYPTION_HEADER,
                              CreateMessageWithId(message.message_id));
      return;
    }

    salt = encryption_header_iterator.salt();
    record_size = encryption_header_iterator.rs();

    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";
      std::move(callback).Run(GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER,
                              CreateMessageWithId(message.message_id));
      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;

    if (!crypto_key_header_iterator.dh().empty()) {
      public_key = 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";
      std::move(callback).Run(GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER,
                              CreateMessageWithId(message.message_id));
      return;
    }

    ciphertext = message.raw_data;
    version = GCMMessageCryptographer::Version::DRAFT_03;
  }

  // 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::BindOnce(&GCMEncryptionProvider::DecryptMessageWithKey,
                     weak_ptr_factory_.GetWeakPtr(), message.message_id,
                     message.collapse_key, message.sender_id, std::move(salt),
                     std::move(public_key), record_size, std::move(ciphertext),
                     version, std::move(callback)));
}  // namespace gcm

void GCMEncryptionProvider::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) {
  DCHECK(key_store_);
  key_store_->GetKeys(
      app_id, authorized_entity,
      false /* fallback_to_empty_authorized_entity */,
      base::BindOnce(&GCMEncryptionProvider::EncryptMessageWithKey,
                     weak_ptr_factory_.GetWeakPtr(), app_id, authorized_entity,
                     p256dh, auth_secret, message, std::move(callback)));
}

void GCMEncryptionProvider::DidCreateEncryptionInfo(
    EncryptionInfoCallback callback,
    std::optional<crypto::keypair::PrivateKey> key,
    const std::string& auth_secret) {
  if (!key) {
    std::move(callback).Run(std::string() /* p256dh */,
                            std::string() /* auth_secret */);
    return;
  }

  std::string uncompressed(
      base::as_string_view(key->ToUncompressedX962Point()));
  std::move(callback).Run(std::move(uncompressed), auth_secret);
}

void GCMEncryptionProvider::DecryptMessageWithKey(
    const std::string& message_id,
    const std::string& collapse_key,
    const std::string& sender_id,
    const std::string& salt,
    const std::string& public_key,
    uint32_t record_size,
    const std::string& ciphertext,
    GCMMessageCryptographer::Version version,
    DecryptMessageCallback callback,
    std::optional<crypto::keypair::PrivateKey> key,
    const std::string& auth_secret) {
  if (!key) {
    DLOG(ERROR) << "Unable to retrieve the keys for the incoming message.";
    std::move(callback).Run(GCMDecryptionResult::NO_KEYS,
                            CreateMessageWithId(message_id));
    return;
  }

  std::string shared_secret;
  if (!ComputeSharedP256Secret(*key, public_key, &shared_secret)) {
    DLOG(ERROR) << "Unable to calculate the shared secret.";
    std::move(callback).Run(GCMDecryptionResult::INVALID_SHARED_SECRET,
                            CreateMessageWithId(message_id));
    return;
  }

  std::string plaintext;

  GCMMessageCryptographer cryptographer(version);

  std::string exported_public_key(
      base::as_string_view(key->ToUncompressedX962Point()));
  if (!cryptographer.Decrypt(exported_public_key, public_key, shared_secret,
                             auth_secret, salt, ciphertext, record_size,
                             &plaintext)) {
    DLOG(ERROR) << "Unable to decrypt the incoming data.";
    std::move(callback).Run(GCMDecryptionResult::INVALID_PAYLOAD,
                            CreateMessageWithId(message_id));
    return;
  }

  IncomingMessage decrypted_message;
  decrypted_message.message_id = message_id;
  decrypted_message.collapse_key = collapse_key;
  decrypted_message.sender_id = 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());

  std::move(callback).Run(version == GCMMessageCryptographer::Version::DRAFT_03
                              ? GCMDecryptionResult::DECRYPTED_DRAFT_03
                              : GCMDecryptionResult::DECRYPTED_DRAFT_08,
                          std::move(decrypted_message));
}

void GCMEncryptionProvider::EncryptMessageWithKey(
    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,
    std::optional<crypto::keypair::PrivateKey> key,
    const std::string& sender_auth_secret) {
  if (!key) {
    DLOG(ERROR) << "Unable to retrieve the keys for the outgoing message.";
    std::move(callback).Run(GCMEncryptionResult::NO_KEYS, std::string());
    return;
  }

  base::expected<std::string, Rfc8291EncryptionError> encrypted =
      EncryptPayloadWithRfc8291(message, p256dh, auth_secret, *key);

  if (!encrypted.has_value()) {
    DLOG(ERROR) << "Unable to encrypt the outgoing GCM message: "
                << static_cast<int>(encrypted.error());
    GCMEncryptionResult error_result = GCMEncryptionResult::ENCRYPTION_FAILED;
    if (encrypted.error() == Rfc8291EncryptionError::kKeyDerivationFailed) {
      error_result = GCMEncryptionResult::INVALID_SHARED_SECRET;
    }
    std::move(callback).Run(error_result, std::string());
    return;
  }

  std::move(callback).Run(GCMEncryptionResult::ENCRYPTED_DRAFT_08,
                          std::move(encrypted.value()));
}

}  // namespace gcm
