/*
 * Copyright (C) 2024 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef LC_PKCS7_GENERATOR_H
#define LC_PKCS7_GENERATOR_H

#include "lc_pkcs7_common.h"

#ifdef __cplusplus
extern "C" {
#endif

/// \cond DO_NOT_DOCUMENT
struct lc_pkcs7_generate_context {
	/*
	 * Message being converted into PKCS#7 blob
	 */
	const struct lc_pkcs7_message *pkcs7;

	/*
	 * Iterator over the additional certificates to place their public key
	 * information into the PKCS#7 message.
	 */
	const struct lc_x509_certificate *current_x509;

	/*
	 * Iterator over the signer certificates to perform the actual signature
	 * operation.
	 */
	const struct lc_pkcs7_signed_info *current_sinfo;

	unsigned long aa_set_applied;
	uint16_t subject_attrib_processed;

	/* Authenticated Attribute data (or NULL) */
	const struct lc_hash *authattr_hash;
	size_t authattrs_digest_size;
	size_t authattrs_size;
	uint8_t authattrs_digest[LC_SHA_MAX_SIZE_DIGEST];
	uint8_t authattrs[LC_PKCS7_AUTHATTRS_MAX_SIZE];

	/**********************************************************************
	 * Caller-provided data
	 **********************************************************************/

	/*
	 * SignedData
	 */
	enum OID signed_info_data_type; /* Type of Data */
};
/// \endcond

/** @defgroup PKCS7Gen PKCS#7 Message Generate Handling
 *
 * Concept of PKCS#7 message generation handling in leancrypto
 *
 * The leancrypto library provides an PKCS#7 generator which can create
 * PKCS#7 messages. The generator does not enforce any PKCS#7 limitations and
 * thus allows the caller to generate any combination of field offered by the
 * PKCS#7 specification. To appropriately use the PKCS#7 parser, please consider
 * the following rules:
 *
 * 1. The generated PKCS#7 data blob is independent of the original PKCS#7
 *    certificate data structure.
 *
 * 2. The generator does not allocate any memory. All memory MUST be provided
 *    by the caller. Thus, if the caller provides insufficient memory, the
 *    generator will return -EOVERFLOW.
 *
 * 3. Before invoking the PKCS#7 generator, the caller must allocate an
 *    \p pkcs7_message data structure (e.g. on stack) and fill it with the
 *    numerous setter functions to add data.
 *
 * 4. The \p pkcs7_message data structure should be released at the end of the
 *    operation with \p lc_pkcs7_message_clear.
 */

/**
 * @ingroup PKCS7Gen
 * @brief Encode a PKCS#7 message
 *
 * The function generates a PKCS#7 data blob from the filled PKCS#7 data
 * structure.
 *
 * Note, this is a simplified version of \p lc_pkcs7_encode_ctx where the
 * PKCS#7 blob is generated in a standard way. \p lc_pkcs7_encode_ctx allows
 * the caller to alter the generation process by setting information in the
 * provided \p ctx.
 *
 * The signature of the data using the signer is created within this call.
 *
 * @param [in] pkcs7 The data structure that is filled by the caller before this
 *		     invocation using the various setter functions.
 * @param [in,out] data Raw PKCS#7 data blob in DER / BER format - the caller
 *			must provide the memory
 * @param [in,out] avail_datalen Length of the raw PKCS#7 certificate buffer that
 *				 is free (the input value must be equal to the
 * 				 \p data buffer size, the output refers to how
 *				 many bytes are unused)
 *
 * @return 0 on success or < 0 on error
 */
int lc_pkcs7_encode(const struct lc_pkcs7_message *pkcs7, uint8_t *data,
		    size_t *avail_datalen);

/**
 * @ingroup PKCS7Gen
 * @brief Initialize a context for encoding a PKCS#7 message
 *
 * This function initializes a context where the memory is provided by the
 * caller. Before invoking \p lc_pkcs7_encode_ctx, the caller may invoke
 * different lc_pkcs7_encode_ctx_* functions to alter how the PKCS#7 message
 * is generated.
 *
 * When no further settings are applied on \p ctx, and \p lc_pkcs7_encode_ctx
 * is invoked immediately after the initialization, the same behavior as
 * \p lc_pkcs7_encode is triggered.
 *
 * @param [in,out] ctx The context data structure shaping the PKCS#7 message
 *		       generation.
 *
 * @return 0 on success or < 0 on error
 */
int lc_pkcs7_encode_ctx_init(struct lc_pkcs7_generate_context *ctx);

/**
 * @ingroup PKCS7Gen
 * @brief Clear the context for encoding a PKCS#7 message
 *
 * @param [in] ctx Context to be cleared
 */
void lc_pkcs7_encode_ctx_clear(struct lc_pkcs7_generate_context *ctx);

/**
 * @ingroup PKCS7Gen
 * @brief Set the PKCS#7 message definition
 *
 * The PKCS#7 message is generated based on this definition.
 *
 * @param [in] ctx The context data structure shaping the PKCS#7 message
 *		   generation.
 * @param [in] pkcs7 The data structure that is filled by the caller before this
 *		     invocation using the various setter functions.
 *
 * @return 0 on success or < 0 on error
 */
int lc_pkcs7_encode_ctx_set_pkcs7(struct lc_pkcs7_generate_context *ctx,
				  const struct lc_pkcs7_message *pkcs7);

/**
 * @ingroup PKCS7Gen
 * @brief Set the PKCS#7 SignedData contentType
 *
 * By default the OID for generic data is set.
 *
 * @param [in] ctx The context data structure shaping the PKCS#7 message
 *		   generation.
 * @param [in] oid Data type OID
 *
 * @return 0 on success or < 0 on error
 */

int lc_pkcs7_encode_ctx_set_signer_data_type(
	struct lc_pkcs7_generate_context *ctx, enum OID oid);

/**
 * @ingroup PKCS7Gen
 * @brief Encode a PKCS#7 message using the provided context.
 *
 * The function generates a PKCS#7 data blob from the filled PKCS#7 data
 * structure.
 *
 * Note, this is an enhanced version of \p lc_pkcs7_encode where the
 * PKCS#7 message is generated by applying the settings defined with \p ctx.
 *
 * The signature of the data using the signer is created within this call.
 *
 * @param [in] ctx The context data structure shaping the PKCS#7 message
 *		   generation.
 * @param [in,out] data Raw PKCS#7 data blob in DER / BER format - the caller
 *			must provide the memory
 * @param [in,out] avail_datalen Length of the raw PKCS#7 certificate buffer that
 *				 is free (the input value must be equal to the
 * 				 \p data buffer size, the output refers to how
 *				 many bytes are unused)
 *
 * @return 0 on success or < 0 on error
 */
int lc_pkcs7_encode_ctx(struct lc_pkcs7_generate_context *ctx, uint8_t *data,
			size_t *avail_datalen);

/**
 * @ingroup PKCS7Gen
 * @brief Set an PKCS#7 certificate to be added to a PKCS#7 message
 *
 * With this call, additional certificates can be supplied that shall be added
 * to the PKCS#7 message.
 *
 * The X.509 certificate associated with the signer is automatically be added as
 * it is registered with \p lc_pkcs7_set_signer. Therefore, it SHALL NOT be
 * added with this call.
 *
 * \note The caller must retain the \p x509 structure for the lifetime of the
 * \p pkcs7 structure.
 *
 * @param [out] pkcs7 PKCS#7 structure that shall receive the signer
 * @param [in] x509 PKCS#7 certificate
 *
 * @return 0 on success, < 0 on error
 */
int lc_pkcs7_set_certificate(struct lc_pkcs7_message *pkcs7,
			     struct lc_x509_certificate *x509);

/**
 * @ingroup PKCS7Gen
 * @brief Set an PKCS#7 certificate as signer for a PKCS#7 message
 *
 * The certificate MUST have a public and secret key set to be added. This
 * function implies that the data to be protected is signed with the private
 * key supplied by this call. Furthermore, the associated X.509 certificate is
 * added to the PKCS#7 message.
 *
 * \note The caller must retain the \p x509_with_sk structure for the lifetime
 * of the \p pkcs7 structure.
 *
 * @param [out] pkcs7 PKCS#7 structure that shall receive the signer
 * @param [in] x509_with_sk PKCS#7 certificate with secret key to be used as
 *			    signer
 * @param [in] signing_hash With this parameter, the signing hash MAY be
 *			    specified by the caller. If this is NULL, the
 *			    default message digest is used. Note, the message
 *			    digest algorithm must be capable of delivering at
 *			    least twice the classic security strength of the
 *			    signature algorithm. This is checked with this
 *			    function and returns -ENOPKG if the requirement is
 *			    not met.
 * @param [in] auth_attribute Specify which authenticated attributes are to be
 *			      generated. When set to 0, no authenticated
 *			      attributes are generated. RFC8419, RFC9814 and
 *			      RFC9882 mandate the use of authenticated
 *			      attributes which impplies that in order to be
 *			      compliant to CMS, this field needs to be set.
 *			      \note When authenticated attributes are to be
 *			      generated, the caller may provide a
 *			      \p signing_hash. If none is provided, the default
 *			      hash type for the given signature mechanism is
 *			      applied.
 *			      The following attributes are allowed:
 *			      \p sinfo_has_content_type - set content type
 *			      \p sinfo_has_signing_time - set signing time
 *
 * @return 0 on success, < 0 on error
 */
int lc_pkcs7_set_signer(struct lc_pkcs7_message *pkcs7,
			struct lc_x509_certificate *x509_with_sk,
			const struct lc_hash *signing_hash,
			unsigned long auth_attribute);

enum lc_pkcs7_set_data_flags {
	/** Define no flags */
	lc_pkcs7_set_data_noflag,
	/** Embed data into PKCS#7 message */
	lc_pkcs7_set_data_embed,
};

/**
 * @ingroup PKCS7Gen
 * @brief Set the data to be signed with PKCS#7
 *
 * \note The caller must retain the \p data for the lifetime of the \p pkcs7
 * structure.
 *
 * @param [in] pkcs7 PKCS#7 data structure to be filled
 * @param [in] data Pointer to the data to be signed
 * @param [in] data_len Size of the data buffer
 * @param [in] flags Flags to be set
 *
 * @return 0 on success, < 0 on error
 */
int lc_pkcs7_set_data(struct lc_pkcs7_message *pkcs7, const uint8_t *data,
		      size_t data_len, enum lc_pkcs7_set_data_flags flags);

/**
 * @ingroup PKCS7Gen
 * @brief Set the data to be signed with PKCS#7
 *
 * \note The caller must retain the \p data for the lifetime of the \p pkcs7
 * structure.
 *
 * @param [in] pkcs7 PKCS#7 data structure to be filled
 * @param [in] data Pointer to the data to be signed
 * @param [in] data_len Size of the data buffer
 * @param [in] flags Flags to be set
 * @param [in] data_type OID of the data type to set to
 *
 * @return 0 on success, < 0 on error
 */
int lc_pkcs7_set_data_with_type(struct lc_pkcs7_message *pkcs7,
				const uint8_t *data, size_t data_len,
				enum lc_pkcs7_set_data_flags flags,
				enum OID data_type);
#ifdef __cplusplus
}
#endif

#endif /* LC_PKCS7_GENERATOR_H */
