/*
 * SigV4 Library v1.2.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file sigv4.h
 * @brief Interface for the SigV4 Library.
 */

#ifndef SIGV4_H_
#define SIGV4_H_

/* Standard includes. */
#include <stdint.h>
#include <stddef.h>

/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */

/* SIGV4_DO_NOT_USE_CUSTOM_CONFIG allows building of the SigV4 library without a
 * config file. If a config file is provided, the SIGV4_DO_NOT_USE_CUSTOM_CONFIG
 * macro must not be defined.
 */
#ifndef SIGV4_DO_NOT_USE_CUSTOM_CONFIG
    #include "sigv4_config.h"
#endif

/* Include config defaults header to get default values of configurations not
 * defined in sigv4_config.h file. */
#include "sigv4_config_defaults.h"

/* Convenience macros for library optimization */

/** @addtogroup sigv4_constants
 *  @{
 */
#define SIGV4_AWS4_HMAC_SHA256                      "AWS4-HMAC-SHA256"                                              /**< AWS identifier for SHA256 signing algorithm. */
#define SIGV4_AWS4_HMAC_SHA256_LENGTH               ( sizeof( SIGV4_AWS4_HMAC_SHA256 ) - 1U )                       /**< Length of AWS identifier for SHA256 signing algorithm. */
#define SIGV4_HTTP_X_AMZ_DATE_HEADER                "x-amz-date"                                                    /**< AWS identifier for HTTP date header. */
#define SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER      "x-amz-security-token"                                          /**< AWS identifier for security token. */

#define SIGV4_STREAMING_AWS4_HMAC_SHA256_PAYLOAD    "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"                            /**< S3 identifier for chunked payloads. */
/* MISRA Ref 5.4.1 [Macro identifiers] */
/* More details at: https://github.com/aws/SigV4-for-AWS-IoT-embedded-sdk/blob/main/MISRA.md#rule-54 */
/* coverity[other_declaration] */
#define SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER           "x-amz-content-sha256"                                     /**< S3 identifier for streaming requests. */
#define SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER_LENGTH    ( sizeof( SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER ) - 1U )  /**< Length of S3 identifier for streaming requests. */
#define SIGV4_HTTP_X_AMZ_STORAGE_CLASS_HEADER            "x-amz-storage-class"                                      /**< S3 identifier for reduced streaming redundancy. */

#define SIGV4_ACCESS_KEY_ID_LENGTH                       20U                                                        /**< Length of access key ID. */
#define SIGV4_SECRET_ACCESS_KEY_LENGTH                   40U                                                        /**< Length of secret access key. */

#define SIGV4_ISO_STRING_LEN                             16U                                                        /**< Length of ISO 8601 date string. */
#define SIGV4_EXPECTED_LEN_RFC_3339                      20U                                                        /**< Length of RFC 3339 date input. */
#define SIGV4_EXPECTED_LEN_RFC_5322                      29U
/**< Length of RFC 5322 date input. */

/** @}*/

/**
 * @defgroup sigv4_canonical_flags SigV4HttpParameters_t Flags
 * @brief Flags for SigV4HttpParameters_t.flags. These flags inform the library
 * of parameters already in canonical form.
 *
 * Flags should be bitwise-ORed with each other to change the behavior of
 * #SigV4_GenerateHTTPAuthorization.
 */

/**
 * @ingroup sigv4_canonical_flags
 * @brief Set this flag to indicate that the HTTP request path input is already
 * canonicalized.
 *
 * This flag is valid only for #SigV4HttpParameters_t.flags.
 */
#define SIGV4_HTTP_PATH_IS_CANONICAL_FLAG        0x1U

/**
 * @ingroup sigv4_canonical_flags
 * @brief Set this flag to indicate that the HTTP request query input is already
 * canonicalized.
 *
 * This flag is valid only for #SigV4HttpParameters_t.flags.
 */
#define SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG       0x2U

/**
 * @ingroup sigv4_canonical_flags
 * @brief Set this flag to indicate that the HTTP request headers input is
 * already canonicalized.
 *
 * This flag is valid only for #SigV4HttpParameters_t.flags.
 */
#define SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG    0x4U

/**
 * @ingroup sigv4_canonical_flags
 * @brief Set this flag to indicate that the HTTP request payload is
 * already hashed.
 *
 * This flag is valid only for #SigV4HttpParameters_t.flags.
 */
#define SIGV4_HTTP_PAYLOAD_IS_HASH               0x8U

/**
 * @ingroup sigv4_canonical_flags
 * @brief Set this flag to indicate that the HTTP request path, query, and
 * headers are all already canonicalized.
 *
 * This flag is valid only for #SigV4HttpParameters_t.flags.
 */
#define SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG        0x7U

/**
 * @ingroup sigv4_enum_types
 * @brief Return status of the SigV4 Library.
 */
typedef enum SigV4Status
{
    /**
     * @brief The SigV4 library function completed successfully.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     * - #SigV4_AwsIotDateToIso8601
     */
    SigV4Success,

    /**
     * @brief The SigV4 library function received an invalid input
     * parameter.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     * - #SigV4_AwsIotDateToIso8601
     */
    SigV4InvalidParameter,

    /**
     * @brief The application buffer was not large enough for the specified hash
     * function.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     */
    SigV4InsufficientMemory,

    /**
     * @brief An error occurred while formatting the provided date header.
     *
     * Functions that may return this value:
     * - #SigV4_AwsIotDateToIso8601
     */
    SigV4ISOFormattingError,

    /**
     * @brief The maximum number of header parameters was exceeded while parsing
     * the http header string passed to the library.
     * The maximum number of supported HTTP headers can be configured
     * with the SIGV4_MAX_HTTP_HEADER_COUNT macro in the library config file
     * passed by the application.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     */
    SigV4MaxHeaderPairCountExceeded,

    /**
     * @brief The maximum number of query parameters was exceeded while parsing
     * the query string passed to the library.
     * The maximum number of supported query parameters can be configured
     * with the SIGV4_MAX_QUERY_PAIR_COUNT macro in the library config file
     * passed by the application.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     */
    SigV4MaxQueryPairCountExceeded,

    /**
     * @brief An error occurred while performing a hash operation.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     */
    SigV4HashError,

    /**
     * @brief HTTP headers parsed to the library are invalid.
     *
     * Functions that may return this value:
     * - #SigV4_GenerateHTTPAuthorization
     */
    SigV4InvalidHttpHeaders
} SigV4Status_t;

/**
 * @ingroup sigv4_struct_types
 * @brief The cryptography interface used to supply the user-defined hash
 * implementation.
 */
typedef struct SigV4CryptoInterface
{
    /**
     * @brief Initializes the @p pHashContext.
     *
     * @param[in] pHashContext Context used to maintain the hash's current state
     * during incremental updates.
     *
     * @return Zero on success, all other return values are failures.
     */
    int32_t ( * hashInit )( void * pHashContext );

    /**
     * @brief Calculates an ongoing hash update (SHA-256, for example).
     *
     * @param[in] pHashContext Context used to maintain the hash's current state
     * during incremental updates.
     * @param[in] pInput Buffer holding the data to hash.
     * @param[in] inputLen length of the input buffer data.
     *
     * @return Zero on success, all other return values are failures.
     */
    int32_t ( * hashUpdate )( void * pHashContext,
                              const uint8_t * pInput,
                              size_t inputLen );

    /**
     * @brief Calculates the final binary digest of the hash from the context.
     *
     * @param[in] pHashContext Context used to maintain the hash's current state
     * during incremental updates.
     * @param[out] pOutput The buffer used to place final hash binary digest
     * output.
     * @param[in] outputLen The length of the pOutput buffer, which must be
     * larger than the hash digest length specified in
     * #SIGV4_HASH_MAX_DIGEST_LENGTH.
     *
     * @return Zero on success, all other return values are failures.
     */
    int32_t ( * hashFinal )( void * pHashContext,
                             uint8_t * pOutput,
                             size_t outputLen );

    /**
     * @brief Context for the hashInit, hashUpdate, and hashFinal interfaces.
     */
    void * pHashContext;

    /**
     * @brief The block length of the hash function.
     */
    size_t hashBlockLen;

    /**
     * @brief The digest length of the hash function.
     */
    size_t hashDigestLen;
} SigV4CryptoInterface_t;

/**
 * @ingroup sigv4_struct_types
 * @brief Configurations of the HTTP request used to create the Canonical
 * Request.
 */
typedef struct SigV4HttpParameters
{
    const char * pHttpMethod; /**< @brief The HTTP method: GET, POST, PUT, etc. */
    size_t httpMethodLen;     /**< @brief Length of pHttpMethod. */

    /**
     * @brief These flags are used to indicate if the path, query, or headers are already
     * in the canonical form. This is to bypass the internal sorting, white space
     * trimming, and encoding done by the library. This is a performance optimization
     * option. Please see https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
     * for information on generating a canonical path, query, and headers string.
     * - #SIGV4_HTTP_PATH_IS_CANONICAL_FLAG     0x1
     * - #SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG    0x2
     * - #SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG 0x4
     * - #SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG     0x7
     */
    uint32_t flags;

    /**
     * @brief The path in the HTTP request. This is the absolute request URI,
     * which contains everything in the URI following the HTTP host until the
     * question mark character ("?") that begins any query string parameters
     * (e.g. "/path/to/item.txt"). If SIGV4_HTTP_PATH_IS_CANONICAL_FLAG is set,
     * then this input must already be in canonical form.
     *
     * @note If there exists no path for the HTTP request, then this can be
     * NULL.
     */
    const char * pPath;
    size_t pathLen; /**< @brief Length of pPath. */

    /**
     * @brief The HTTP request query from the URL, if it exists. This contains all
     * characters following the question mark character ("?") that denotes the start
     * of the query. If SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG is set, then this input
     * must already be in canonical form.
     *
     * @note If the HTTP request does not contain query string, this can
     * be NULL.
     */
    const char * pQuery;
    size_t queryLen; /**< @brief Length of pQuery. */

    /**
     * @brief The headers from the HTTP request that we want to sign. This
     * should be the raw headers in HTTP request format. If
     * SIGV4_HTTP_HEADERS_IS_CANONICAL_FLAG is set, then this input must
     * already be in canonical form.
     *
     * @note The headers data MUST NOT be empty. For HTTP/1.1 requests, it is
     * required that the "host" header MUST be part of the SigV4 signature.
     */
    const char * pHeaders;
    size_t headersLen; /**< @brief Length of pHeaders. */

    /**
     * @brief The HTTP response body, if one exists (ex. PUT request). If this
     * body is chunked, then this field should be set with
     * STREAMING-AWS4-HMAC-SHA256-PAYLOAD.
     */
    const char * pPayload;
    size_t payloadLen; /**< @brief Length of pPayload. */
} SigV4HttpParameters_t;

/**
 * @ingroup sigv4_struct_types
 * @brief Configurations for the AWS credentials used to generate the Signing
 * Key.
 */
typedef struct SigV4Credentials
{
    /**
     * @brief The pAccessKeyId MUST be at least 16 characters long
     * but not more than 128 characters long.
     */
    const char * pAccessKeyId;
    size_t accessKeyIdLen; /**< @brief Length of pAccessKeyId. */

    /**
     * @brief The pSecretAccessKey MUST be at least 40 characters long.
     */
    const char * pSecretAccessKey;
    size_t secretAccessKeyLen; /**< @brief Length of pSecretAccessKey. */
} SigV4Credentials_t;

/**
 * @ingroup sigv4_struct_types
 * @brief Complete configurations required for generating "String to Sign" and
 * "Signing Key" values.
 *
 * Consists of parameter structures #SigV4Credentials_t,
 * #SigV4CryptoInterface_t, and #SigV4HttpParameters_t, along with date, region,
 * and service specifications.
 */
typedef struct SigV4Parameters
{
    /**
     * @brief The AccessKeyId, SecretAccessKey, and SecurityToken used to
     * generate the Authorization header.
     */
    SigV4Credentials_t * pCredentials;

    /**
     * @brief The date in ISO 8601 format, e.g. "20150830T123600Z". This is
     * always 16 characters long.
     */
    const char * pDateIso8601;

    /**
     * @brief The algorithm used for SigV4 authentication. If set to NULL,
     * this will automatically be set to "AWS4-HMAC-SHA256" by default.
     */
    const char * pAlgorithm;

    size_t algorithmLen; /**< @brief Length of pAlgorithm. */

    /**
     * @brief The target AWS region for the request. Please see
     * https://docs.aws.amazon.com/general/latest/gr/rande.html for a list of
     * region names and codes.
     */
    const char * pRegion;
    size_t regionLen; /**< @brief Length of pRegion. */

    /**
     * @brief The target AWS service for the request. The service name can be
     * found as the first segment of the service endpoint. Please see
     * https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html
     * (https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html)
     * for your service of interest.
     */
    const char * pService;
    size_t serviceLen; /**< @brief Length of pService. */

    /**
     * @brief The cryptography interface.
     */
    SigV4CryptoInterface_t * pCryptoInterface;

    /**
     * @brief HTTP specific SigV4 parameters for canonical request calculation.
     */
    SigV4HttpParameters_t * pHttpParameters;
} SigV4Parameters_t;

/**
 * @brief Generates the HTTP Authorization header value.
 * @note The API does not support HTTP headers containing empty HTTP header keys or values.
 *
 * @param[in] pParams Parameters for generating the SigV4 signature.
 * @param[out] pAuthBuf Buffer to hold the generated Authorization header value.
 * @param[in, out] authBufLen Input: the length of @p pAuthBuf, output: the length
 * of the authorization value written to the buffer.
 * @param[out] pSignature Location of the signature in the authorization string.
 * @param[out] signatureLen The length of @p pSignature.
 *
 * @return #SigV4Success if successful, error code otherwise.
 *
 * <b>Example</b>
 * @code{c}
 * // The following example shows how to use the SigV4_GenerateHTTPAuthorization
 * // function to generate the HTTP Authorization header value for HTTP requests
 * // to AWS services requiring SigV4 authentication.
 *
 * SigV4Status_t status = SigV4Success;
 *
 * // Buffer to hold the authorization header.
 * char pSigv4Auth[ 2048U ];
 * size_t sigv4AuthLen = sizeof( pSigv4Auth );
 *
 * // Pointer to signature in the Authorization header that will be populated in
 * // pSigv4Auth by the SigV4_GenerateHTTPAuthorization API function.
 * char * signature = NULL;
 * size_t signatureLen = 0;
 *
 * SigV4Parameters_t sigv4Params =
 * {
 *     // Parsed temporary credentials obtained from AWS IoT Credential Provider.
 *     .pCredentials     = &sigv4Creds,
 *     // Date in ISO8601 format.
 *     .pDateIso8601     = pDateISO8601,
 *     // The AWS region for the request.
 *     .pRegion          = AWS_REGION,
 *     .regionLen        = strlen( AWS_REGION ),
 *     // The AWS service for the request.
 *     .pService         = AWS_SERVICE_NAME,
 *     .serviceLen       = strlen( AWS_SERVICE_NAME ),
 *     // SigV4 crypto interface. See SigV4CryptoInterface_t interface documentation.
 *     .pCryptoInterface = &cryptoInterface,
 *     // HTTP parameters for the HTTP request to generate a SigV4 authorization header for.
 *     .pHttpParameters  = &sigv4HttpParams
 * };
 *
 * status = SigV4_GenerateHTTPAuthorization( &sigv4Params, pSigv4Auth, &sigv4AuthLen, &signature, &signatureLen );
 *
 * if( status != SigV4Success )
 * {
 *    // Failed to generate authorization header.
 * }
 * @endcode
 */
/* @[declare_sigV4_generateHTTPAuthorization_function] */
SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams,
                                               char * pAuthBuf,
                                               size_t * authBufLen,
                                               char ** pSignature,
                                               size_t * signatureLen );
/* @[declare_sigV4_generateHTTPAuthorization_function] */

/**
 * @brief Parse the date header value from the AWS IoT response, and generate
 * the formatted ISO 8601 date required for authentication.
 *
 * This is an optional utility function available to the application, to assist
 * with formatting of the date header obtained from AWS IoT (when requesting a
 * temporary token or sending a POST request).
 *
 * AWS SigV4 authentication requires an ISO 8601 date to be present in the
 * "x-amz-date" request header, as well as in the credential scope (must be
 * identical). For additional information on date handling, please see
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html.
 *
 * Acceptable Input Formats:
 * - RFC 5322 (ex. "Thu, 18 Jan 2018 09:18:06 GMT"), the preferred format in
 *   HTTP 'Date' response headers. If using this format, the date parameter
 *   should match "***, DD 'MMM' YYYY hh:mm:ss GMT" exactly.
 * - RFC 3339 (ex. "2018-01-18T09:18:06Z"), found occasionally in 'Date' and
 *   expiration headers. If using this format, the date parameter should match
 *   "YYYY-MM-DD'T'hh:mm:ss'Z'" exactly.
 *
 * Formatted Output:
 * - The ISO8601-formatted date will be returned in the form
 *   "YYYYMMDD'T'HHMMSS'Z'" (ex. "20180118T091806Z").
 *
 * @param[in] pDate The date header (in
 * [RFC 3339](https://tools.ietf.org/html/rfc3339) or
 * [RFC 5322](https://tools.ietf.org/html/rfc5322) formats). An acceptable date
 * header can be found in the HTTP response returned by AWS IoT. This value
 * should use UTC (with no time-zone offset), and be exactly 20 or 29 characters
 * in length (excluding the null character), to comply with RFC 3339 and RFC
 * 5322 formats, respectively.
 * @param[in] dateLen The length of the pDate header value. Must be either
 * SIGV4_EXPECTED_LEN_RFC_3339 or SIGV4_EXPECTED_LEN_RFC_5322, for valid input
 * parameters.
 * @param[out] pDateISO8601 The formatted ISO8601-compliant date. The date value
 * written to this buffer will be exactly 16 characters in length, to comply
 * with the ISO8601 standard required for SigV4 authentication.
 * @param[in] dateISO8601Len The length of buffer pDateISO8601. Must be at least
 * SIGV4_ISO_STRING_LEN bytes, for valid input parameters.
 *
 * @return #SigV4Success code if successful, error code otherwise.
 *
 *
 * <b>Example</b>
 * @code{c}
 * // The following example shows how to use the SigV4_AwsIotDateToIso8601
 * // function to convert an AWS IoT date header value to a ISO 8601 date.
 *
 * SigV4Status_t status = SigV4Success;
 * char pDateISO8601[SIGV4_ISO_STRING_LEN] = {0};
 * size_t pDateISO8601Len = SIGV4_ISO_STRING_LEN;
 *
 * // pDate and dateLen are the date header and length which are parsed from
 * // an AWS IoT Credential Provider HTTP response, using an HTTP library.
 * status = SigV4_AwsIotDateToIso8601( pDate, dateLen, pDateISO8601, pDateISO8601Len );
 *
 * if( status != SigV4Success )
 * {
 *    // Failed to parse date
 * }
 * @endcode
 */
/* @[declare_sigV4_awsIotDateToIso8601_function] */
SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate,
                                         size_t dateLen,
                                         char * pDateISO8601,
                                         size_t dateISO8601Len );
/* @[declare_sigV4_awsIotDateToIso8601_function] */

/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */

#endif /* SIGV4_H_ */
