/*
 * zgetdump - Tool for copying and converting System z dumps
 *
 * Utilities to decrypt secure execution guest dumps.
 *
 * Copyright IBM Corp. 2022
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#ifndef PV_UTILS_H
#define PV_UTILS_H

#include <stdbool.h>

#include <openssl/evp.h>

#include "lib/util_log.h"
#include "libpv/crypto.h"

#include "df_elf.h"
#include "dfi.h"
#include "pv_defs.h"
#include <stdint.h>

#define PV_INVAL_PAGE_STATE 0UL
#define PV_ENCRYPTED_PAGE   (1UL << 0)
#define PV_SHARED_PAGE	    (1UL << 1)
#define PV_ZERO_PAGE	    (1UL << 2)
#define PV_MAPPED_PAGE	    (1UL << 3)

#define FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(type) type##_flat_struct_cleanse_free
#define DEFINE_FLAT_STRUCT_CLEANSE_FREE(type, free_func)                                           \
	static void FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(type)(type * obj)                           \
	{                                                                                          \
		if (!obj)                                                                          \
			return;                                                                    \
		OPENSSL_cleanse(obj, sizeof(type));                                                \
		free_func(obj);                                                                    \
	}

/* The typedef is required to be able to define a autoptr cleanup
 * function using glib2.
 */
typedef struct dfi_cpu dfi_cpu_t;

DEFINE_FLAT_STRUCT_CLEANSE_FREE(dfi_cpu_t, dfi_cpu_free)
DEFINE_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_data_v1_t, g_free)
DEFINE_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_confidential_area_v1_t, g_free)

WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(dfi_cpu_t, FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(dfi_cpu_t))
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(
	pv_dump_completion_data_v1_t,
	FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_data_v1_t))
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(
	pv_dump_completion_confidential_area_v1_t,
	FUNC_NAME_FLAT_STRUCT_CLEANSE_FREE(pv_dump_completion_confidential_area_v1_t))

typedef struct _pv_crypto_ctx pv_crypto_ctx_t;
typedef struct _storage_state_mmap storage_state_mmap_t;
struct _pv_elf_ctx {
	BIO *output;
	u64 elf_load_off;
	pv_crypto_ctx_t *pv_ctx;
	storage_state_mmap_t *storage_state_data;
};
typedef struct _pv_elf_ctx pv_elf_ctx_t;

void pv_dump_completion_free(pv_dump_completion_t *cpl);
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_dump_completion_t, pv_dump_completion_free)

/**
 * pv_derive_dump_key_v1:
 * @cpl_data: v1 dump completion data
 * @cck: Customer communication key
 * @error: return location for a #GError
 *
 * Derives an 32 byte AES-256-GCM key using the key derivation function HDKF
 * together with the customer communication key (CCK) @cck as input keying
 * material, and the seed from the completion configuration dump @cpl_data as salt.
 *
 * Returns: 32 byte AES-256-GCM key on success, %NULL if an error occurred
 */
GBytes *pv_derive_dump_key_v1(const pv_dump_completion_data_v1_t *cpl_data, GBytes *cck,
			      GError **error);

/**
 * pv_decrypt_dump_completion_v1:
 * @cpl_data: v1 dump completion data
 * @dump_key: Dump key
 * @error: return location for a #GError
 *
 * Decrypts and authenticates the completion configuration dump data @cpl_data
 * using the AES-256-GCM @dump_key.
 *
 * Returns: Decrypted confidential area of the completion configuration dump on success,
 *          %NULL if an error occurred
 */
pv_dump_completion_v1_t *pv_decrypt_dump_completion_v1(const pv_dump_completion_data_v1_t *cpl_data,
						       GBytes *dump_key, GError **error);

dfi_cpu_t *pv_decrypt_cpu_note_data(const unsigned int expected_version, GBytes *cpu_note,
				    GBytes *dump_key, GError **error);

/**
 * pv_decrypt_cpu_dump_area_v1:
 * @cpu_dump_area: a v1 CPU dump area
 * @dump_key: Dump key
 * @error: return location for a #GError
 *
 * Decrypts and authenticates a secure CPU dump using AES-256-GCM with specified
 * dump_key.
 *
 * Returns: Decrypted part of the CPU dump on success, %NULL if an error occurred
 */
GBytes *pv_decrypt_cpu_dump_area_v1(const pv_cpu_dump_v1_t *cpu_dump_area, GBytes *dump_key,
				    GError **error);

/**
 * pv_dfi_cpu_from_pv_cpu:
 * @pv_cpu: PV CPU data
 *
 * Convert PV CPU data to internal dfi_cpu struct.
 *
 * Returns: #dfi_cpu struct
 */
dfi_cpu_t *pv_dfi_cpu_from_pv_cpu(const pv_cpu_dump_confidential_area_v1_t *pv_cpu);

/**
 * pv_crypto_ctx_new:
 * @input: a #BIO input
 * @key: (array length=key_size): byte data of the key
 * @key_size: size of @key in bytes
 * @nonce: The PV tweak nonce
 * @mode: encrypt or decrypt mode
 * @error: return location for a #GError
 *
 * Create a PV crypto context using @input as the input, @key as the cipher key
 * and @nonce as the nonce for deriving the tweaks.
 *
 * Returns: a new #pv_crypto_ctx_t
 */
pv_crypto_ctx_t *pv_crypto_ctx_new(BIO *input, const unsigned char *key, size_t key_size,
				   const pv_tweak_nonce_t *nonce, enum PvCryptoMode mode,
				   GError **error);

/**
 * pv_crypto_ctx_free:
 * @ctx: a #pv_crypto_ctx_t
 *
 * Free all resources and the memory allocated for the #pv_crypto_ctx_t
 */
void pv_crypto_ctx_free(pv_crypto_ctx_t *ctx);

/**
 * pv_process_pglist:
 * @ctx: a #pv_crypto_ctx_t context
 * @output: Output
 * @tweaks_components: (array length=tweaks_len) (element-type pv_tweak_component_t): tweaks
 * components to be used for the decryption
 * @tweaks_components_len: the length of @tweaks and the length of the page list to be processed
 * @input_off: the file offset of the encrypted memory passed in @ctx to decrypt
 * @error: return location for a #GError
 *
 * Decrypts a page block (e.g. 256 pages).
 *
 * Returns: number of processed pages on success, -1 if an error occurred
 */
ssize_t pv_process_pglist(pv_crypto_ctx_t *ctx, BIO *output,
			  const pv_tweak_component_t *tweak_components, size_t tweak_components_len,
			  long input_off, GError **error);

/**
 * calculate_tweak:
 *
 * calculates the bitwise or of tweak component and nonce.
 */
void calculate_tweak(const pv_tweak_component_t *tweak, const pv_tweak_nonce_t *nonce,
		     pv_tweak_t *out);
/**
 * pv_read_page:
 * @output: Input
 * @output: Output
 *
 * Read one page data from @input and write it to @output.
 *
 * Returns: The number of bytes read for success, -1 if an error occurred
 */
int pv_read_page(BIO *input, BIO *output, GError **error);

/**
 * pv_page_state:
 *
 * Get the page state of the tweak component.
 */
unsigned long pv_page_state(const pv_tweak_component_t *comp);

/**
 * pv_get_tweak_components:
 * @storage_state_data: input data
 * @page_idx: Page index of the fist page
 * @page_cnt: number of tweak components that can be at least accessed
 *
 * get a pointer to the page states of the given pages.
 *
 * Returns: pointer to page states or NULL in case of an error
 *
 */
const pv_tweak_component_t *pv_get_tweak_components(storage_state_mmap_t *storage_state_data,
						    u64 page_idx, u64 page_cnt);

/**
 * pv_elf_ctx_new:
 * fd: file descriptor of elf file
 * @completion_confidential_decr:  content of ection pv_meta size must be
 * sizeof(pv_dump_completion_confidential_area_t)
 * @storage_state_data: mmap to pv_mem_meta
 * @load_offset: offset of LOAD segment in fd
 * @error: return value for GError
 *
 * creates a new elf context for processing the elf file
 *
 * Returns: new elf context or NULL in case of error
 */
pv_elf_ctx_t *
pv_elf_ctx_new(const int fd,
	       const pv_dump_completion_confidential_area_v1_t *completion_confidential_decr,
	       storage_state_mmap_t *storage_state_data, const u64 load_offset, GError **error);

/**
 * pv_info_free:
 * frees the elf context
 */
void pv_elf_ctx_free(pv_elf_ctx_t *p);

/**
 * pv_is_pv_elf:
 * @shdrs: list of section headers
 * @shnum: size of shdrs list
 * @shstrtab: content of shstrtab (section hdr string table)
 * @shstrtab_size: size of shstrtab
 *
 * Returns: true in case it is a PV ELF vmcore dump, false otherwise.
 */
bool pv_is_pv_elf(const Elf64_Shdr *shdrs, const unsigned int shnum, const char *shstrtab,
		  const size_t shstrtab_size);

/**
 * pv_process_section_data:
 *
 * Uses the ELF sections data to derive dump key, using the provided
 * customer-communication-key. It copies the decrypted completion
 * data into @completion_decr and mmaps storage state tweaks to storage
 * state data.
 *
 * The caller is responsible for cleanup and overwriting.
 *
 * Returns: 0 in case of success, -1 otherwise.
 */
int pv_process_section_data(const int fd, const size_t file_size, GBytes *completion_sec,
			    const u64 storage_state_offset, const size_t storage_state_size,
			    GBytes *cck, pv_dump_completion_t **completion_decr, GBytes **dump_key,
			    storage_state_mmap_t **storage_state_data, GError **error);

/**
 * pv_elf_read:
 *
 * elf_ctx: elf context
 * @start_addr: first address to read from
 * @dst: target
 * @size: num bytes to read
 * @error: return value for GError
 *
 * Read size bytes from start addr in elf_ctx and save it into dest.
 * Will decrypt pages if defined by page state.
 *
 * Returns: 0 in case of success, -1 otherwise.
 */
int pv_elf_read(const pv_elf_ctx_t *elf_ctx, const u64 start_addr, void *dst, const u64 size,
		GError **error);

/**
 * storage_state_mmap_new:
 * @fd: file descriptor for which to create the mapping
 * @file_size: file size in bytes
 * @offset: offset in fd to pv_mem_meta section
 * @size: size of mapping  (and pv_mem_meta section)
 * @error: return value for GError
 *
 * Returns: new storage_state_mmap context
 */
storage_state_mmap_t *storage_state_mmap_new(const int fd, const size_t file_size, const u64 offset,
					     const u64 size, GError **error);

/**
 * storage_state_ref:
 *
 * increase the reference counter by one.
 *
 * Returns: pointer to storage_state
 */
storage_state_mmap_t *storage_state_mmap_ref(storage_state_mmap_t *storage_state);

/**
 * storage_state_unref:
 *
 * Decreases the reference counter by one.
 * If the counter is now zero storage_state will be unmapped and freed.
 * If the munmap fails, an error will be logged.
 */
void storage_state_mmap_unref(storage_state_mmap_t *storage_state);

#define ZDUMP_PV_UTILS_ERROR g_quark_from_static_string("zdump-pv-utils")
typedef enum {
	ZDUMP_ERR_BIO,
	ZDUMP_ERR_BIO_FAIL,
	ZDUMP_ERR_BIO_KEY,
	ZDUMP_ERR_CORRUPTED_COMPL_DATA,
	ZDUMP_ERR_CORRUPTED_NOTE,
	ZDUMP_ERR_CRYPTO_CTX,
	ZDUMP_ERR_ELF_CTX,
	ZDUMP_ERR_ELF_CTX_BIO,
	ZDUMP_ERR_ELF_READ,
	ZDUMP_ERR_ELF_READ_END_ADDR_OVERFLOW,
	ZDUMP_ERR_MMAP,
	ZDUMP_ERR_PAGELIST_ELF_OFFSET_TOO_LARGE,
	ZDUMP_ERR_PAGE_END_ADDR_OVERFLOW,
	ZDUMP_ERR_PAGE_START_ADDR_OVERFLOW,
	ZDUMP_ERR_PGLIST_BIO,
	ZDUMP_ERR_PGLIST_INVAL,
	ZDUMP_ERR_PGLIST_INVAL_STATE,
	ZDUMP_ERR_PROC_SECT,
	ZDUMP_ERR_TWEAK,
	ZDUMP_ERR_UNSUPP_COMPL_VER,
	ZDUMP_ERR_UNSUPP_SEC_CPU_VER,
	ZDUMP_ERR_WRONG_AAD_SIZE,
	ZDUMP_ERR_WRONG_CCK_SIZE,
	ZDUMP_ERR_WRONG_CONFIDENTIAL_SIZE,
	ZDUMP_ERR_WRONG_NOTE_SIZE,
	ZDUMP_ERR_WRONG_TAG_SIZE,
} zdump_pv_utils_error_e;

WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_crypto_ctx_t, pv_crypto_ctx_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pv_elf_ctx_t, pv_elf_ctx_free)
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(storage_state_mmap_t, storage_state_mmap_unref)

#endif /* PV_UTILS_H */
