// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "azure/attestation/attestation_client_models.hpp"
#include "azure/attestation/attestation_client_options.hpp"

#include <azure/core/context.hpp>
#include <azure/core/internal/tracing/service_tracing.hpp>
#include <azure/core/url.hpp>

#include <string>

namespace Azure { namespace Core { namespace Http { namespace _internal {
  class HttpPipeline;
}}}} // namespace Azure::Core::Http::_internal

namespace Azure { namespace Security { namespace Attestation {

  /**
   *
   * The AttestationAsyncClient implements the functionality required by the "Attest" family of
   * APIs.
   *
   * An enclave (or Trusted Execution Environment) is a chunk of code that is isolated from the host
   * (think: "encrypted VM" or "encrypted container"). But there's one key attribute of the enclave:
   * It is encrypted.That means that
   * if data is sent from the enclave, there is no way of knowing that the data came from the
   * enclave. And even worse, there is no way of securely communicating with the enclave (since the
   * enclave is fully isolated from the host, all information passed into the enclave has to go
   * through its host first).
   *
   * To solve the communication problem, the Attest API can be used to facilitate what is
   * known as the "Secure Key Release" (SKR) protocol.
   *
   * There are 4 parties involved in an attestation operation:
   *
   * - The host (which hosts the enclave)
   * - The enclave (which is the enclave :) - encrypted, nobody can see what goes on inside it),
   * - The "verifier" which verifies the evidence from the enclave (this is the attestation service)
   * and generates a token which can be received by a relying party, and
   * - The "relying party" which will interpret the token from the service. For the Secure Key
   * Release Protocol, this is the entity which wishes to communicate with the enclave.
   *
   * It's possible that all these parties are on the same computer, it's possible they're on
   * multiple computers.<br> It's possible that the host is also the relying party. It's possible
   * that the relying party is a component like Azure Managed HSM.
   *
   * There are three primary pieces of data received by the service for the Attest family of APIs.
   * All of them are arrays of bytes, and all of them originate from code running in the enclave
   * (thus they need to be treated as opaque arrays of bytes by the SDK):
   *
   * -# Evidence. For Intel SGX enclaves, this has two forms, either an SGX 'Quote' or an
   * OpenEnclave 'Report'. It is required for attestation operations.
   * -# InitTimeData - This is data which is specified at Initialization Time. It is optional
   * (and not currently supported on all enclave types in Azure)
   * -# RunTimeData - this is data which is specified at the time the quote is generated (at
   * "runtime"). It is optional, but required for the Secure Key Release protocol.
   *
   * The Evidence is cryptographically signed by a known authority (for Intel SGX Quotes or
   * OpenEnclave reports, this is a key owned by Intel which represents that the SGX enclave is
   * valid and can be trusted).<br> The core idea for all attestation operations is to take
   * advantage of a region within the Evidence which is controlled by enclave. For SGX Enclaves,
   * this is the 64 bytes of "user data" contained within SGX quote.
   *
   * For the Secure Key Release protocol, code inside the enclave generates an asymmetric key and
   * serializes the public key into a byte buffer. It then calculates the SHA256 hash of the
   * serialized key and creates a quote containing that SHA256 hash. We now have a cryptographically
   * validated indication that the contents of the byte buffer was known inside the enclave.
   *
   * The enclave then hands the byte buffer and the quote to its host. The host sends the quote and
   * byte buffer as the "RunTime Data" to the via the AttestationClient.AttestSgxEnclave  or
   * AttestationClient.AttestOpenEnclave} API. Assuming the byte buffer and quote are
   * valid, and the quote contains the hash of the byte buffer, the attestation service responds
   * with an AttestationToken signed by the attestation service, whose body is an
   * AttestationResult.
   *
   * The token generated also includes the contents of the InitTimeData and/or RunTimeData if it was
   * provided in the Attest API call.
   *
   * The host then sends the token to the relying party.  The relying party verifies the token
   * and verifies the claims within the token indicate that the enclave is the correct enclave.
   * It then takes the key from the token and uses it to encrypt the data to be sent to the
   * enclave and sends that back to the host, which passes it into the enclave.
   *
   *
   * That completes the secure key release protocol.
   *
   * When the Attestation Token is generated by the attestation service, as mentioned, it contains
   * the InitTime and RunTime data.
   *
   * There are two possible representations for RunTime Data in the attestation token, depending on
   * the requirements of the relying party:<br> The first is as JSON formatted data. That can be
   * convenient if the relying party expects to receive its public key as a JSON Web Key The second
   * is as a binary blob of data. That is needed if either the data sent by the enclave isn't a JSON
   * object - for instance, if the RunTime data contained an asymmetric key which is formatted as a
   * PEM encoded key, it should be interpreted as a binary blob
   *
   * If you ask for the RunTime data to be included in the token as binary, then it will be
   * base64url encoded in the "x-ms-maa-enclavehelddata" claim in the output token (the
   * AttestationResult::EnclaveHeldData property).
   *
   * If you ask for the RunTime data to be included in the token as JSON, then it will be included
   * in the "x-ms-maa-runtimeClaims" claim in the output token (the AttestationResult::RunTimeClaims
   * property).
   *
   * In addition to the Attest APIs, the AttestationClient object also contains helper APIs
   * which can be used to retrieve the OpenId Metadata document and signing keys from the service.
   *
   * The OpenId Metadata document contains properties which describe the attestation service.
   *
   * The Attestation Signing Keys describe the keys which will be used to sign tokens generated by
   * the attestation service. All tokens emitted by the attestation service will be signed by one
   * of the certificates listed in the attestation signing keys.
   *
   */

  class AttestationClient final {

  public:
    /** @brief Construct a new Attestation Client object
     *
     * @details Constructs a new attestation client. Follows the
     * factory pattern in [C++ Core Guidelines
     * C.50](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c50-use-a-factory-function-if-you-need-virtual-behavior-during-initialization)
     *
     * @param endpoint The URL address where the client will send the requests to.
     * @param credential The authentication method to use (required for TPM attestation). If the
     * credential parameter is not supplied, the connection will be unauthenticated.
     * @param options The options to customize the client behavior.
     * @param context The context for cancelling long running operations.
     * @return The newly created client.
     */
    static AttestationClient Create(
        std::string const& endpoint,
        std::shared_ptr<Core::Credentials::TokenCredential const> credential,
        AttestationClientOptions const& options = AttestationClientOptions{},
        Azure::Core::Context const& context = Azure::Core::Context{});

    /** @brief Construct a new anonymous Attestation Client object
     *
     * @details Constructs a new anonymous (unauthenticated) attestation client. Follows the
     * factory pattern in [C++ Core Guidelines
     * C.50](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c50-use-a-factory-function-if-you-need-virtual-behavior-during-initialization)
     *
     * @param endpoint The URL address where the client will send the requests to.
     * @param options The options to customize the client behavior.
     * @param context The context for cancelling long running operations.
     * @return The newly created attestation client.
     *
     * @note TPM attestation requires an authenticated attestation client.
     *
     */
    static AttestationClient Create(
        std::string const& endpoint,
        AttestationClientOptions options = AttestationClientOptions{},
        Azure::Core::Context const& context = Azure::Core::Context{});

    /**
     * @brief Destructor.
     *
     */
    virtual ~AttestationClient() = default;

    /** @brief Construct a new Attestation Client object from an existing attestation client.
     *
     * @param attestationClient An existing attestation client.
     */
    AttestationClient(AttestationClient const& attestationClient) = default;

    /** @brief Returns the Absolute URL for this attestation client.
     *
     * @returns The absolute URL for this attestation client.
     */
    std::string const Endpoint() const { return m_endpoint.GetAbsoluteUrl(); }

    /**
     * Retrieves metadata about the attestation signing keys in use by the attestation service.
     *
     * Retrieve the OpenID metadata for this attestation service instance..
     *
     * @return an \ref Models::OpenIdMetadata object containing metadata about the
     * specified service instance.
     */
    Response<Models::OpenIdMetadata> GetOpenIdMetadata(
        Azure::Core::Context const& context = Azure::Core::Context::ApplicationContext) const;

    /**
     * @brief Retrieve the attestation signing certificates for this attestation instance.
     *
     * @returns A Models::AttestationSigningCertificateResult containing a list of certificates one
     * of which will be used to validate tokens received by the attestation service.
     */
    Response<Models::TokenValidationCertificateResult> GetTokenValidationCertificates(
        Azure::Core::Context const& context = Azure::Core::Context{}) const;

    /**
     * @brief Attest an SGX enclave, returning an attestation token representing the result
     * of the attestation operation.
     *
     * @param sgxQuoteToAttest - SGX Quote to be validated by the attestation service.
     * @param options - Options to the attestation request (runtime data, inittime data, etc).
     * @param context - Context for the operation.
     *
     * @returns Response<AttestationToken<AttestationResult>> - The result of the
     * attestation operation.
     *
     */
    Response<Models::AttestationToken<Models::AttestationResult>> AttestSgxEnclave(
        std::vector<uint8_t> const& sgxQuoteToAttest,
        AttestSgxEnclaveOptions options = AttestSgxEnclaveOptions{},
        Azure::Core::Context const& context = Azure::Core::Context{}) const;

    /**
     * @brief Attest an OpenEnclave report, returning an attestation token representing the result
     * of the attestation operation.
     *
     * @param openEnclaveReportToAttest - OpenEnclave Report to be validated by the attestation
     * service.
     * @param options - Options to the attestation request (runtime data, inittime data, etc).
     * @param context - Context for the operation.
     *
     * @returns Response<AttestationToken<AttestationResult>> - The result of the attestation
     * operation

     */
    Response<Models::AttestationToken<Models::AttestationResult>> AttestOpenEnclave(
        std::vector<uint8_t> const& openEnclaveReportToAttest,
        AttestOpenEnclaveOptions options = AttestOpenEnclaveOptions{},
        Azure::Core::Context const& context = Azure::Core::Context{}) const;

    /**
     * @brief Sends TPM-based attestation data to the service.
     * The TPM attestation protocol is defined
     * [here](https://docs.microsoft.com/azure/attestation/virtualization-based-security-protocol')
     *
     *
     * @param dataToAttest - Attestation request data.
     * @param options - Options to the attestation request.
     * @param context - Context for the operation.
     *
     * @return Response<TpmAttestationResult> - The result of the attestation operation
     */
    Response<Models::TpmAttestationResult> AttestTpm(
        std::vector<uint8_t> const& dataToAttest,
        AttestTpmOptions const& options = AttestTpmOptions{},
        Azure::Core::Context const& context = Azure::Core::Context{}) const;

  private:
    Azure::Core::Url m_endpoint;
    std::string m_apiVersion;
    std::shared_ptr<Azure::Core::Http::_internal::HttpPipeline> m_pipeline;
    AttestationTokenValidationOptions m_tokenValidationOptions;
    std::vector<Models::AttestationSigner> m_attestationSigners;
    Azure::Core::Tracing::_internal::TracingContextFactory m_tracingFactory;

    /** @brief Construct a new Attestation Client object
     *
     * @param endpoint The URL address where the client will send the requests to.
     * @param credential The authentication method to use (required for TPM attestation).
     * @param options The options to customize the client behavior.
     */
    AttestationClient(
        std::string const& endpoint,
        std::shared_ptr<Core::Credentials::TokenCredential const> credential,
        AttestationClientOptions options = AttestationClientOptions{});

    /**
     * @brief Retrieves the information needed to validate a response from the attestation service.
     *
     * @note: This method MUST be called before any calls to the attestation service which must be
     * validated.
     */
    void RetrieveResponseValidationCollateral(
        Azure::Core::Context const& context = Azure::Core::Context{});
  };

}}} // namespace Azure::Security::Attestation
