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

#include "remoting/protocol/spake2_authenticator.h"

#include <utility>

#include "base/base64.h"
#include "base/containers/span.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/string_view_util.h"
#include "crypto/hmac.h"
#include "crypto/secure_util.h"
#include "remoting/base/constants.h"
#include "remoting/base/rsa_key_pair.h"
#include "remoting/protocol/authenticator.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"

namespace remoting::protocol {

namespace {

// Each peer sends 2 messages: <spake-message> and <verification-hash>. The
// content of <spake-message> is the output of SPAKE2_generate_msg() and must
// be passed to SPAKE2_process_msg() on the other end. This is enough to
// generate authentication key. <verification-hash> is sent to confirm that both
// ends get the same authentication key (which means they both know the
// password). This verification hash is calculated in
// CalculateVerificationHash() as follows:
//    HMAC_SHA256(auth_key, ("host"|"client") + local_jid.length() + local_jid +
//                remote_jid.length() + remote_jid)
// where auth_key is the key produced by SPAKE2.

}  // namespace

// static
std::unique_ptr<Authenticator> Spake2Authenticator::CreateForClient(
    const std::string& local_id,
    const std::string& remote_id,
    const std::string& shared_secret,
    Authenticator::State initial_state) {
  return base::WrapUnique(new Spake2Authenticator(
      local_id, remote_id, shared_secret, false, initial_state));
}

// static
std::unique_ptr<Authenticator> Spake2Authenticator::CreateForHost(
    const std::string& local_id,
    const std::string& remote_id,
    const std::string& local_cert,
    scoped_refptr<RsaKeyPair> key_pair,
    const std::string& shared_secret,
    Authenticator::State initial_state) {
  std::unique_ptr<Spake2Authenticator> result(new Spake2Authenticator(
      local_id, remote_id, shared_secret, true, initial_state));
  result->local_cert_ = local_cert;
  result->local_key_pair_ = key_pair;
  return std::move(result);
}

Spake2Authenticator::Spake2Authenticator(const std::string& local_id,
                                         const std::string& remote_id,
                                         const std::string& shared_secret,
                                         bool is_host,
                                         Authenticator::State initial_state)
    : local_id_(local_id),
      remote_id_(remote_id),
      shared_secret_(shared_secret),
      is_host_(is_host),
      state_(initial_state) {
  spake2_context_ = SPAKE2_CTX_new(
      is_host ? spake2_role_bob : spake2_role_alice,
      reinterpret_cast<const uint8_t*>(local_id_.data()), local_id_.size(),
      reinterpret_cast<const uint8_t*>(remote_id_.data()), remote_id_.size());

  // Generate first message and push it to |pending_messages_|.
  uint8_t message[SPAKE2_MAX_MSG_SIZE];
  size_t message_size;
  int result = SPAKE2_generate_msg(
      spake2_context_, message, &message_size, sizeof(message),
      reinterpret_cast<const uint8_t*>(shared_secret_.data()),
      shared_secret_.size());
  CHECK(result);
  local_spake_message_.assign(reinterpret_cast<char*>(message), message_size);
}

Spake2Authenticator::~Spake2Authenticator() {
  SPAKE2_CTX_free(spake2_context_);
}

CredentialsType Spake2Authenticator::credentials_type() const {
  return CredentialsType::SHARED_SECRET;
}

const Authenticator& Spake2Authenticator::implementing_authenticator() const {
  return *this;
}

Authenticator::State Spake2Authenticator::state() const {
  if (state_ == ACCEPTED && !outgoing_verification_hash_.empty()) {
    return MESSAGE_READY;
  }
  return state_;
}

bool Spake2Authenticator::started() const {
  return started_;
}

Authenticator::RejectionReason Spake2Authenticator::rejection_reason() const {
  DCHECK_EQ(state(), REJECTED);
  return rejection_reason_;
}

Authenticator::RejectionDetails Spake2Authenticator::rejection_details() const {
  DCHECK_EQ(state(), REJECTED);
  return rejection_details_;
}

void Spake2Authenticator::ProcessMessage(const JingleAuthentication& message,
                                         base::OnceClosure resume_callback) {
  ProcessMessageInternal(message);
  std::move(resume_callback).Run();
}

void Spake2Authenticator::ProcessMessageInternal(
    const JingleAuthentication& message) {
  DCHECK_EQ(state(), WAITING_MESSAGE);

  // Only update |remote_cert_| if a certificate is provided in the message.
  // This prevents overwriting a valid certificate with an empty value in
  // multi-step exchanges where the certificate is only provided in the first
  // message.
  if (!message.certificate.empty()) {
    remote_cert_.assign(
        reinterpret_cast<const char*>(message.certificate.data()),
        message.certificate.size());
  }

  // Client always expects certificate in the first message.
  if (!is_host_ && remote_cert_.empty()) {
    state_ = REJECTED;
    rejection_reason_ = RejectionReason::INVALID_STATE;
    rejection_details_ = RejectionDetails("No valid host certificate.");
    return;
  }

  // |auth_key_| is generated when <spake-message> is received.
  if (auth_key_.empty()) {
    if (message.spake_message.empty()) {
      state_ = REJECTED;
      rejection_reason_ = RejectionReason::INVALID_ARGUMENT;
      rejection_details_ = RejectionDetails("<spake-message> not found.");
      return;
    }
    uint8_t key[SPAKE2_MAX_KEY_SIZE];
    size_t key_size;
    started_ = true;
    int result = SPAKE2_process_msg(
        spake2_context_, key, &key_size, sizeof(key),
        reinterpret_cast<const uint8_t*>(message.spake_message.data()),
        message.spake_message.size());
    if (!result) {
      state_ = REJECTED;
      rejection_reason_ = RejectionReason::INVALID_CREDENTIALS;
      rejection_details_ =
          RejectionDetails("Failed to process SPAKE2 message.");
      return;
    }
    CHECK(key_size);
    auth_key_.assign(reinterpret_cast<char*>(key), key_size);

    outgoing_verification_hash_ =
        CalculateVerificationHash(is_host_, local_id_, remote_id_);
    expected_verification_hash_ =
        CalculateVerificationHash(!is_host_, remote_id_, local_id_);
  } else if (!message.spake_message.empty()) {
    state_ = REJECTED;
    rejection_reason_ = RejectionReason::INVALID_STATE;
    rejection_details_ =
        RejectionDetails("Received duplicate <spake-message>.");
    return;
  }

  if (spake_message_sent_ && message.verification_hash.empty()) {
    state_ = REJECTED;
    rejection_reason_ = RejectionReason::INVALID_STATE;
    rejection_details_ =
        RejectionDetails("Didn't receive <verification-hash> when expected.");
    return;
  }

  if (!message.verification_hash.empty()) {
    if (!crypto::SecureMemEqual(
            base::as_byte_span(message.verification_hash),
            base::as_byte_span(expected_verification_hash_))) {
      state_ = REJECTED;
      rejection_reason_ = RejectionReason::INVALID_CREDENTIALS;
      rejection_details_ = RejectionDetails("Verification hash mismatched.");
      return;
    }
    state_ = ACCEPTED;
    return;
  }

  state_ = MESSAGE_READY;
}

JingleAuthentication Spake2Authenticator::GetNextMessage() {
  DCHECK_EQ(state(), MESSAGE_READY);

  JingleAuthentication message;

  if (!spake_message_sent_) {
    if (!local_cert_.empty()) {
      message.certificate.assign(local_cert_.begin(), local_cert_.end());
    }

    message.spake_message.assign(local_spake_message_.begin(),
                                 local_spake_message_.end());

    spake_message_sent_ = true;
  }

  if (!outgoing_verification_hash_.empty()) {
    message.verification_hash.assign(outgoing_verification_hash_.begin(),
                                     outgoing_verification_hash_.end());
    outgoing_verification_hash_.clear();
  }

  if (state_ != ACCEPTED) {
    state_ = WAITING_MESSAGE;
  }
  return message;
}

const std::string& Spake2Authenticator::GetAuthKey() const {
  return auth_key_;
}

const SessionPolicies* Spake2Authenticator::GetSessionPolicies() const {
  return nullptr;
}

std::string Spake2Authenticator::CalculateVerificationHash(
    bool from_host,
    const std::string& local_id,
    const std::string& remote_id) {
  crypto::hmac::HmacSigner signer(crypto::hash::kSha256,
                                  base::as_byte_span(auth_key_));
  std::string_view direction = from_host ? "host" : "client";
  signer.Update(base::as_byte_span(direction));
  signer.Update(base::U32ToBigEndian(local_id.size()));
  signer.Update(base::as_byte_span(local_id));
  signer.Update(base::U32ToBigEndian(remote_id.size()));
  signer.Update(base::as_byte_span(remote_id));
  std::array<uint8_t, crypto::hash::kSha256Size> result;
  signer.Finish(result);
  return std::string(base::as_string_view(result));
}

}  // namespace remoting::protocol
