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

#ifndef DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
#define DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_

#include <stdint.h>

#include <array>
#include <optional>
#include <string>
#include <vector>

#include "base/component_export.h"
#include "base/containers/span.h"
#include "crypto/sha2.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/fido_constants.h"
#include "device/fido/json_request.h"
#include "device/fido/large_blob.h"
#include "device/fido/pin.h"
#include "device/fido/prf_input.h"
#include "device/fido/public_key_credential_descriptor.h"

#if BUILDFLAG(ARKWEB_FIDO)
#include "device/fido/ctap_get_assertion_request_extra.h"
#endif // BUILDFLAG(ARKWEB_FIDO)

namespace cbor {
class Value;
}

namespace device {

// CtapGetAssertionOptions contains values that are pertinent to a
// |GetAssertionTask|, but are not specific to an individual
// authenticatorGetAssertion command, i.e. would not be directly serialised into
// the CBOR.
struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionOptions {
  CtapGetAssertionOptions();
  CtapGetAssertionOptions(const CtapGetAssertionOptions&);
  CtapGetAssertionOptions(CtapGetAssertionOptions&&);
  ~CtapGetAssertionOptions();

  // The JSON form of the request. (May be nullptr.)
  scoped_refptr<JSONRequest> json;

  // The PUAT used for the request. The caller is expected to set this if needed
  // with the correct permissions. Obtain from |FidoAuthenticator::GetPINToken|.
  std::optional<pin::TokenResponse> pin_uv_auth_token;

  // The ephemeral key use to encrypt PIN material.
  std::optional<pin::KeyAgreementResponse> pin_key_agreement;

  // prf_inputs may contain a default PRFInput without a |credential_id|. If so,
  // it will be the first element and all others will have |credential_id|s.
  // Elements are sorted by |credential_id|s, where present.
  std::vector<PRFInput> prf_inputs;

  // If true, attempt to read a large blob.
  bool large_blob_read = false;

  // If set, attempt to write a large blob.
  std::optional<std::vector<uint8_t>> large_blob_write;

  // Indicates whether the request was created in an off-the-record
  // BrowserContext (e.g. Chrome Incognito mode).
  bool is_off_the_record_context = false;

  // The set of hints passed by the relying party.
  // https://w3c.github.io/webauthn/#enum-hints.
  std::vector<FidoTransportProtocol> hints;
};

// Object that encapsulates request parameters for AuthenticatorGetAssertion as
// specified in the CTAP spec.
// https://fidoalliance.org/specs/fido-v2.0-rd-20161004/fido-client-to-authenticator-protocol-v2.0-rd-20161004.html#authenticatorgetassertion
struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
 public:
  using ClientDataHash = std::array<uint8_t, kClientDataHashLength>;

  // ParseOpts are optional parameters passed to Parse().
  struct ParseOpts {
    // reject_all_extensions makes parsing fail if any extensions are present.
    bool reject_all_extensions = false;
  };

  // HMACSecret contains the inputs to the hmac-secret extension:
  // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension
  struct HMACSecret {
    HMACSecret(base::span<const uint8_t, kP256X962Length> public_key_x962,
               base::span<const uint8_t> encrypted_salts,
               base::span<const uint8_t> salts_auth,
               std::optional<PINUVAuthProtocol> pin_protocol);
    HMACSecret(const HMACSecret&);
    ~HMACSecret();
    HMACSecret& operator=(const HMACSecret&);

    std::array<uint8_t, kP256X962Length> public_key_x962;
    std::vector<uint8_t> encrypted_salts;
    std::vector<uint8_t> salts_auth;
    // pin_protocol is ignored during serialisation and the request's PIN
    // protocol will be used instead.
    std::optional<PINUVAuthProtocol> pin_protocol;
  };

  // Decodes a CTAP2 authenticatorGetAssertion request message. The request's
  // |client_data_json| will be empty and |client_data_hash| will be set.
  //
  // A |uv| bit of 0 is mapped to UserVerificationRequirement::kDiscouraged.
  static std::optional<CtapGetAssertionRequest> Parse(
      const cbor::Value::MapValue& request_map) {
    return Parse(request_map, ParseOpts());
  }
  static std::optional<CtapGetAssertionRequest> Parse(
      const cbor::Value::MapValue& request_map,
      const ParseOpts& opts);

  CtapGetAssertionRequest(std::string rp_id, std::string client_data_json);
  CtapGetAssertionRequest(const CtapGetAssertionRequest& that);
  CtapGetAssertionRequest(CtapGetAssertionRequest&& that);
  CtapGetAssertionRequest& operator=(const CtapGetAssertionRequest& other);
  CtapGetAssertionRequest& operator=(CtapGetAssertionRequest&& other);
  ~CtapGetAssertionRequest();

  // This can be constructed with an empty ClientDataJson, but it must be
  // provided before dispatching any authenticators into a RequestHandler that
  // uses this request.
  void SetClientDataJson(std::string client_data_json);

  std::string rp_id;
  std::string client_data_json;
  std::array<uint8_t, kClientDataHashLength> client_data_hash;
  UserVerificationRequirement user_verification =
      UserVerificationRequirement::kDiscouraged;
  bool user_presence_required = true;

  std::vector<PublicKeyCredentialDescriptor> allow_list;
  std::optional<std::vector<uint8_t>> pin_auth;
  std::optional<PINUVAuthProtocol> pin_protocol;
  std::optional<std::vector<CableDiscoveryData>> cable_extension;
  std::optional<std::string> app_id;
  std::optional<std::array<uint8_t, crypto::kSHA256Length>>
      alternative_application_parameter;
  std::optional<HMACSecret> hmac_secret;
  bool large_blob_key = false;
  bool get_cred_blob = false;

  // prf_inputs is non-empty if the `prf` extension is contained in the request.
  // The WebAuthn-level `prf` extension is implemented at the CTAP level by
  // either the `hmac-secret` extension or the `prf` extension. Security keys
  // generally only implement `hmac-secret` and, in this case, values are
  // set in the `CtapGetAssertionOptions` so that the `GetAssertionTask` can
  // send the multiple requests needed to process them. "Large" authenticators,
  // e.g. phones, want all the inputs at once and thus process the CTAP-level
  // `prf` extension.
  std::vector<PRFInput> prf_inputs;

  // These fields indicate that a large-blob operation should be performed
  // using the largeBlob extension that includes largeBlob data directly
  // in getAssertion requests.
  bool large_blob_extension_read = false;
  std::optional<LargeBlob> large_blob_extension_write;

#if BUILDFLAG(ARKWEB_FIDO)
  std::optional<CtapGetAssertionRequestExtra> extra;
#endif // BUILDFLAG(ARKWEB_FIDO)
};

struct CtapGetNextAssertionRequest {};

// Serializes GetAssertion request parameter into CBOR encoded map with
// integer keys and CBOR encoded values as defined by the CTAP spec.
// https://drafts.fidoalliance.org/fido-2/latest/fido-client-to-authenticator-protocol-v2.0-wd-20180305.html#authenticatorGetAssertion
COMPONENT_EXPORT(DEVICE_FIDO)
std::pair<CtapRequestCommand, std::optional<cbor::Value>>
AsCTAPRequestValuePair(const CtapGetAssertionRequest&);

COMPONENT_EXPORT(DEVICE_FIDO)
std::pair<CtapRequestCommand, std::optional<cbor::Value>>
AsCTAPRequestValuePair(const CtapGetNextAssertionRequest&);

}  // namespace device

#endif  // DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_