// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"

#include <array>
#include <utility>

#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chromeos/ash/components/nearby/common/proto/timestamp.pb.h"
#include "components/cross_device/logging/logging.h"
#include "crypto/aead.h"
#include "crypto/aes_ctr.h"
#include "crypto/hmac.h"
#include "crypto/signature_verifier.h"

namespace {

bool IsDataValid(base::Time not_before,
                 base::Time not_after,
                 base::span<const uint8_t> public_key,
                 base::span<const uint8_t> id,
                 base::span<const uint8_t> encrypted_metadata,
                 base::span<const uint8_t> metadata_encryption_key_tag) {
  return not_before < not_after && !public_key.empty() &&
         id.size() == kNearbyShareNumBytesCertificateId &&
         !encrypted_metadata.empty() &&
         metadata_encryption_key_tag.size() ==
             kNearbyShareNumBytesMetadataEncryptionKeyTag;
}

// Attempts to decrypt |encrypted_metadata_key| using the |secret_key|.
// Return std::nullopt if the decryption was unsuccessful.
std::optional<std::vector<uint8_t>> DecryptMetadataKey(
    const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
    base::span<const uint8_t, kNearbyShareNumBytesSecretKey> secret_key) {
  auto counter = DeriveNearbyShareKey<crypto::aes_ctr::kCounterSize>(
      encrypted_metadata_key.salt());

  return crypto::aes_ctr::Decrypt(
      secret_key, counter,
      base::as_byte_span(encrypted_metadata_key.encrypted_key()));
}

// Attempts to decrypt |encrypted_metadata| with |metadata_encryption_key|,
// using |authentication_key| as the IV. Returns std::nullopt if the decryption
// was unsuccessful.
std::optional<std::vector<uint8_t>> DecryptMetadataPayload(
    base::span<const uint8_t> encrypted_metadata,
    base::span<const uint8_t> metadata_encryption_key,
    base::span<const uint8_t, kNearbyShareNumBytesSecretKey> secret_key) {
  const auto key = DeriveNearbyShareKey<kNearbyShareNumBytesAesGcmKey>(
      metadata_encryption_key);
  const auto nonce =
      DeriveNearbyShareKey<kNearbyShareNumBytesAesGcmIv>(secret_key);
  return crypto::aead::Open(crypto::aead::AES_256_GCM, key, encrypted_metadata,
                            nonce, /*associated_data=*/{});
}

// Returns true if the HMAC of |decrypted_metadata_key| is
// |metadata_encryption_key_tag|.
bool VerifyMetadataEncryptionKeyTag(
    base::span<const uint8_t> decrypted_metadata_key,
    base::span<const uint8_t> metadata_encryption_key_tag) {
  const auto tag =
      metadata_encryption_key_tag.to_fixed_extent<crypto::hash::kSha256Size>();

  // This array of 0x00 is used to conform with the GmsCore implementation. This
  // turns HMAC into a slow hash function; see b/433801272.
  constexpr std::array<uint8_t, kNearbyShareNumBytesMetadataEncryptionKeyTag>
      key = {0};
  return tag.has_value() &&
         crypto::hmac::VerifySha256(key, decrypted_metadata_key, *tag);
}

}  // namespace

// static
std::optional<NearbyShareDecryptedPublicCertificate>
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
    const nearby::sharing::proto::PublicCertificate& public_certificate,
    const NearbyShareEncryptedMetadataKey& encrypted_metadata_key) {
  // Note: The PublicCertificate.metadata_encryption_key and
  // PublicCertificate.for_selected_contacts are not returned from the server
  // for remote devices.
  base::Time not_before = base::Time::FromSecondsSinceUnixEpoch(
      public_certificate.start_time().seconds());
  base::Time not_after = base::Time::FromSecondsSinceUnixEpoch(
      public_certificate.end_time().seconds());
  std::vector<uint8_t> public_key(public_certificate.public_key().begin(),
                                  public_certificate.public_key().end());

  auto secret_key = base::as_byte_span(public_certificate.secret_key())
                        .to_fixed_extent<kNearbyShareNumBytesSecretKey>();
  if (!secret_key) {
    return std::nullopt;
  }

  std::vector<uint8_t> id(public_certificate.secret_id().begin(),
                          public_certificate.secret_id().end());
  std::vector<uint8_t> encrypted_metadata(
      public_certificate.encrypted_metadata_bytes().begin(),
      public_certificate.encrypted_metadata_bytes().end());
  std::vector<uint8_t> metadata_encryption_key_tag(
      public_certificate.metadata_encryption_key_tag().begin(),
      public_certificate.metadata_encryption_key_tag().end());

  if (!IsDataValid(not_before, not_after, public_key, id, encrypted_metadata,
                   metadata_encryption_key_tag)) {
    return std::nullopt;
  }

  // Note: Failure to decrypt the metadata key or failure to confirm that the
  // decrypted metadata key agrees with the key commitment tag should not log an
  // error. When another device advertises their encrypted metadata key, we do
  // not know what public certificate that corresponds to. So, we will
  // potentially be calling DecryptPublicCertificate() on all of our public
  // certificates with the same encrypted metadata key until we find the correct
  // one.
  std::optional<std::vector<uint8_t>> decrypted_metadata_key =
      DecryptMetadataKey(encrypted_metadata_key, *secret_key);
  if (!decrypted_metadata_key ||
      !VerifyMetadataEncryptionKeyTag(*decrypted_metadata_key,
                                      metadata_encryption_key_tag)) {
    return std::nullopt;
  }

  // If the key was able to be decrypted, we expect the metadata to be able to
  // be decrypted.
  std::optional<std::vector<uint8_t>> decrypted_metadata_bytes =
      DecryptMetadataPayload(encrypted_metadata, *decrypted_metadata_key,
                             *secret_key);
  if (!decrypted_metadata_bytes) {
    CD_LOG(ERROR, Feature::NS)
        << "Metadata decryption failed: Failed to decrypt metadata "
        << "payload.";
    return std::nullopt;
  }

  nearby::sharing::proto::EncryptedMetadata unencrypted_metadata;
  if (!unencrypted_metadata.ParseFromArray(decrypted_metadata_bytes->data(),
                                           decrypted_metadata_bytes->size())) {
    CD_LOG(ERROR, Feature::NS)
        << "Metadata decryption failed: Failed to parse decrypted "
        << "metadata payload.";
    return std::nullopt;
  }

  return NearbyShareDecryptedPublicCertificate(
      not_before, not_after, *secret_key, std::move(public_key), std::move(id),
      std::move(unencrypted_metadata), public_certificate.for_self_share());
}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    base::Time not_before,
    base::Time not_after,
    base::span<const uint8_t, kNearbyShareNumBytesSecretKey> secret_key,
    std::vector<uint8_t> public_key,
    std::vector<uint8_t> id,
    nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
    bool for_self_share)
    : not_before_(not_before),
      not_after_(not_after),
      public_key_(std::move(public_key)),
      id_(std::move(id)),
      unencrypted_metadata_(std::move(unencrypted_metadata)),
      for_self_share_(for_self_share) {
  base::span(secret_key_).copy_from(secret_key);
}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    const NearbyShareDecryptedPublicCertificate& other) {
  *this = other;
}

NearbyShareDecryptedPublicCertificate&
NearbyShareDecryptedPublicCertificate::operator=(
    const NearbyShareDecryptedPublicCertificate& other) {
  if (this == &other)
    return *this;

  not_before_ = other.not_before_;
  not_after_ = other.not_after_;
  base::span(secret_key_).copy_from(other.secret_key_);
  public_key_ = other.public_key_;
  id_ = other.id_;
  unencrypted_metadata_ = other.unencrypted_metadata_;
  for_self_share_ = other.for_self_share_;
  return *this;
}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    NearbyShareDecryptedPublicCertificate&&) = default;

NearbyShareDecryptedPublicCertificate&
NearbyShareDecryptedPublicCertificate::operator=(
    NearbyShareDecryptedPublicCertificate&&) = default;

NearbyShareDecryptedPublicCertificate::
    ~NearbyShareDecryptedPublicCertificate() = default;

bool NearbyShareDecryptedPublicCertificate::VerifySignature(
    base::span<const uint8_t> payload,
    base::span<const uint8_t> signature) const {
  crypto::SignatureVerifier verifier;
  if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
                           public_key_)) {
    CD_LOG(ERROR, Feature::NS)
        << "Verification failed: Initialization unsuccessful.";
    return false;
  }

  verifier.VerifyUpdate(payload);

  return verifier.VerifyFinal();
}

std::array<uint8_t, kNearbyShareNumBytesAuthenticationTokenHash>
NearbyShareDecryptedPublicCertificate::HashAuthenticationToken(
    base::span<const uint8_t> authentication_token) const {
  return ComputeAuthenticationTokenHash(authentication_token, secret_key_);
}
