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

// This file contains structures to implement the CTAP2 PIN protocol, version
// one. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#authenticatorClientPIN

#ifndef DEVICE_FIDO_PIN_H_
#define DEVICE_FIDO_PIN_H_

#include <stdint.h>

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

#include "base/component_export.h"
#include "base/containers/span.h"
#include "components/cbor/values.h"
#include "device/fido/fido_constants.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace device {
namespace pin {

// The reason we are prompting for a new PIN.
enum class PINEntryReason {
  // Indicates a new PIN is being set.
  kSet,

  // The existing PIN must be changed before using this authenticator.
  kChange,

  // The existing PIN is being collected to prove user verification.
  kChallenge
};

// The errors that may prompt asking for a PIN.
enum class PINEntryError {
  // No error has occurred.
  kNoError,

  // Internal UV is locked, so we are falling back to PIN.
  kInternalUvLocked,

  // The PIN the user entered does not match the authenticator PIN.
  kWrongPIN,

  // The new PIN the user entered is too short.
  kTooShort,

  // The new PIN the user entered contains invalid characters.
  kInvalidCharacters,

  // The new PIN the user entered is the same as the currently set PIN.
  kSameAsCurrentPIN,
};

// Permission list flags. See
// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#permissions
enum class Permissions : uint8_t {
  kMakeCredential = 0x01,
  kGetAssertion = 0x02,
  kCredentialManagement = 0x04,
  kBioEnrollment = 0x08,
  kLargeBlobWrite = 0x10,
};

// Some commands that validate PinUvAuthTokens include this padding to ensure a
// PinUvAuthParam cannot be reused across different commands.
constexpr std::array<uint8_t, 32> kPinUvAuthTokenSafetyPadding = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

// Validates |pin|, returning |kNoError| if valid or an appropriate error code
// otherwise.
COMPONENT_EXPORT(DEVICE_FIDO)
PINEntryError ValidatePIN(
    const std::string& pin,
    uint32_t min_pin_length = kMinPinLength,
    absl::optional<std::string> current_pin = absl::nullopt);

// Like |ValidatePIN| above but takes a wide string.
COMPONENT_EXPORT(DEVICE_FIDO)
PINEntryError ValidatePIN(
    const std::u16string& pin16,
    uint32_t min_pin_length = kMinPinLength,
    absl::optional<std::string> current_pin = absl::nullopt);

// kMinBytes is the minimum number of *bytes* of PIN data that a CTAP2 device
// will accept. Since the PIN is UTF-8 encoded, this could be a single code
// point. However, the platform is supposed to additionally enforce a 4
// *character* minimum
constexpr size_t kMinBytes = 4;
// kMaxBytes is the maximum number of bytes of PIN data that a CTAP2 device will
// accept.
constexpr size_t kMaxBytes = 63;

// EncodeCOSEPublicKey converts an X9.62 public key to a COSE structure.
COMPONENT_EXPORT(DEVICE_FIDO)
cbor::Value::MapValue EncodeCOSEPublicKey(
    base::span<const uint8_t, kP256X962Length> x962);

// PinRetriesRequest asks an authenticator for the number of remaining PIN
// attempts before the device is locked.
struct PinRetriesRequest {
  PINUVAuthProtocol protocol;
};

// UVRetriesRequest asks an authenticator for the number of internal user
// verification attempts before the feature is locked.
struct UvRetriesRequest {
  PINUVAuthProtocol protocol;
};

// RetriesResponse reflects an authenticator's response to a |PinRetriesRequest|
// or a |UvRetriesRequest|.
struct RetriesResponse {
  static absl::optional<RetriesResponse> ParsePinRetries(
      const absl::optional<cbor::Value>& cbor);

  static absl::optional<RetriesResponse> ParseUvRetries(
      const absl::optional<cbor::Value>& cbor);

  // retries is the number of PIN attempts remaining before the authenticator
  // locks.
  int retries;

 private:
  static absl::optional<RetriesResponse> Parse(
      const absl::optional<cbor::Value>& cbor,
      const int retries_key);

  RetriesResponse();
};

// KeyAgreementRequest asks an authenticator for an ephemeral ECDH key for
// encrypting PIN material in future requests.
struct KeyAgreementRequest {
  PINUVAuthProtocol protocol;
};

// KeyAgreementResponse reflects an authenticator's response to a
// |KeyAgreementRequest| and is also used as representation of the
// authenticator's ephemeral key.
struct COMPONENT_EXPORT(DEVICE_FIDO) KeyAgreementResponse {
  static absl::optional<KeyAgreementResponse> Parse(
      const absl::optional<cbor::Value>& cbor);
  static absl::optional<KeyAgreementResponse> ParseFromCOSE(
      const cbor::Value::MapValue& cose_key);

  // X962 returns the public key from the response in X9.62 form.
  std::array<uint8_t, kP256X962Length> X962() const;

  // x and y contain the big-endian coordinates of a P-256 point. It is ensured
  // that this is a valid point on the curve.
  uint8_t x[32], y[32];

 private:
  KeyAgreementResponse();
};

// SetRequest sets an initial PIN on an authenticator. (This is distinct from
// changing a PIN.)
class SetRequest {
 public:
  // IsValid(pin) must be true.
  SetRequest(PINUVAuthProtocol protocol,
             const std::string& pin,
             const KeyAgreementResponse& peer_key);

  friend std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
  AsCTAPRequestValuePair(const SetRequest&);

 private:
  const PINUVAuthProtocol protocol_;
  const KeyAgreementResponse peer_key_;
  uint8_t pin_[kMaxBytes + 1];
};

struct EmptyResponse {
  static absl::optional<EmptyResponse> Parse(
      const absl::optional<cbor::Value>& cbor);
};

// ChangeRequest changes the PIN on an authenticator that already has a PIN set.
// (This is distinct from setting an initial PIN.)
class ChangeRequest {
 public:
  // IsValid(new_pin) must be true.
  ChangeRequest(PINUVAuthProtocol protocol,
                const std::string& old_pin,
                const std::string& new_pin,
                const KeyAgreementResponse& peer_key);

  friend std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
  AsCTAPRequestValuePair(const ChangeRequest&);

 private:
  const PINUVAuthProtocol protocol_;
  const KeyAgreementResponse peer_key_;
  uint8_t old_pin_hash_[16];
  uint8_t new_pin_[kMaxBytes + 1];
};

// ResetRequest resets an authenticator, which should invalidate all
// credentials and clear any configured PIN. This is not strictly a
// PIN-related command, but is generally used to reset a PIN and so is
// included here.
struct ResetRequest {};

using ResetResponse = EmptyResponse;

// TokenRequest requests a pin-token from an authenticator. These tokens can be
// used to show user-verification in other operations, e.g. when getting an
// assertion.
class TokenRequest {
 public:
  TokenRequest(const TokenRequest&) = delete;

  // shared_key returns the shared ECDH key that was used to encrypt the PIN.
  // This is needed to decrypt the response.
  const std::vector<uint8_t>& shared_key() const;

 protected:
  TokenRequest(TokenRequest&&);
  TokenRequest(PINUVAuthProtocol protocol,
               const KeyAgreementResponse& peer_key);
  ~TokenRequest();

  const PINUVAuthProtocol protocol_;
  std::vector<uint8_t> shared_key_;
  std::array<uint8_t, kP256X962Length> public_key_;
};

class PinTokenRequest : public TokenRequest {
 public:
  PinTokenRequest(PINUVAuthProtocol protocol,
                  const std::string& pin,
                  const KeyAgreementResponse& peer_key);
  PinTokenRequest(PinTokenRequest&&);
  PinTokenRequest(const PinTokenRequest&) = delete;
  virtual ~PinTokenRequest();

  friend std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
  AsCTAPRequestValuePair(const PinTokenRequest&);

 protected:
  uint8_t pin_hash_[16];
};

class PinTokenWithPermissionsRequest : public PinTokenRequest {
 public:
  PinTokenWithPermissionsRequest(PINUVAuthProtocol protocol,
                                 const std::string& pin,
                                 const KeyAgreementResponse& peer_key,
                                 base::span<const pin::Permissions> permissions,
                                 const absl::optional<std::string> rp_id);
  PinTokenWithPermissionsRequest(PinTokenWithPermissionsRequest&&);
  PinTokenWithPermissionsRequest(const PinTokenWithPermissionsRequest&) =
      delete;
  ~PinTokenWithPermissionsRequest() override;

  friend std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
  AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest&);

 private:
  uint8_t permissions_;
  absl::optional<std::string> rp_id_;
};

class UvTokenRequest : public TokenRequest {
 public:
  UvTokenRequest(PINUVAuthProtocol protocol,
                 const KeyAgreementResponse& peer_key,
                 absl::optional<std::string> rp_id,
                 base::span<const pin::Permissions> permissions);
  UvTokenRequest(UvTokenRequest&&);
  UvTokenRequest(const UvTokenRequest&) = delete;
  virtual ~UvTokenRequest();

  friend std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
  AsCTAPRequestValuePair(const UvTokenRequest&);

 private:
  absl::optional<std::string> rp_id_;
  uint8_t permissions_;
};

class HMACSecretRequest {
 public:
  HMACSecretRequest(PINUVAuthProtocol protocol,
                    const KeyAgreementResponse& peer_key,
                    base::span<const uint8_t, 32> salt1,
                    const absl::optional<std::array<uint8_t, 32>>& salt2);
  HMACSecretRequest(const HMACSecretRequest&);
  ~HMACSecretRequest();
  HMACSecretRequest& operator=(const HMACSecretRequest&);

  absl::optional<std::vector<uint8_t>> Decrypt(
      base::span<const uint8_t> ciphertext);

 private:
  const PINUVAuthProtocol protocol_;
  std::vector<uint8_t> shared_key_;

 public:
  const std::array<uint8_t, kP256X962Length> public_key_x962;
  const std::vector<uint8_t> encrypted_salts;
  const std::vector<uint8_t> salts_auth;
};

// TokenResponse represents the response to a pin-token request. In order to
// decrypt a response, the shared key from the request is needed. Once a pin-
// token has been decrypted, it can be used to calculate the pinAuth parameters
// needed to show user-verification in future operations.
class COMPONENT_EXPORT(DEVICE_FIDO) TokenResponse {
 public:
  ~TokenResponse();
  TokenResponse(const TokenResponse&);
  TokenResponse& operator=(const TokenResponse&);

  static absl::optional<TokenResponse> Parse(
      PINUVAuthProtocol protocol,
      base::span<const uint8_t> shared_key,
      const absl::optional<cbor::Value>& cbor);

  std::pair<PINUVAuthProtocol, std::vector<uint8_t>> PinAuth(
      base::span<const uint8_t> client_data_hash) const;

  PINUVAuthProtocol protocol() const { return protocol_; }
  const std::vector<uint8_t>& token_for_testing() const { return token_; }

 private:
  explicit TokenResponse(PINUVAuthProtocol protocol);

  PINUVAuthProtocol protocol_;
  std::vector<uint8_t> token_;
};

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const PinRetriesRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const UvRetriesRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const KeyAgreementRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const SetRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const ChangeRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const ResetRequest&);

std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const TokenRequest&);

}  // namespace pin

}  // namespace device

#endif  // DEVICE_FIDO_PIN_H_