/*
 * Copyright (c) 2011-2026 Roumen Petrov.  All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "includes.h"

#ifdef WITH_OPENSSL
#ifdef USE_OPENSSL_ENGINE
# include <openssl/engine.h>
# include <openssl/conf.h>
#endif
#include <openssl/rand.h>
#ifndef HAVE_OPENSSL_INIT_CRYPTO
# include <openssl/err.h>
#endif
#ifdef HAVE_OPENSSL_FIPS_H
/* for FIPS_mode() and ERR_load_FIPS_strings() */
# include <openssl/fips.h>
#endif
#ifdef ENABLE_KEY_PROVIDERS
# include <openssl/provider.h>
# include <openssl/core_names.h>
#endif

#include "evp-compat.h"

#include "log.h"


static int crypto_status = 0;

#ifdef ENABLE_KEY_PROVIDERS
OSSL_LIB_CTX *pkixssh_libctx = NULL;
char *pkixssh_propq = NULL;
#endif
#ifdef HAVE_EVP_KEYMGMT_FETCH
extern char *rsa_prov_name;
#endif


int
ssh_is_fips_enabled(void) {
#ifdef HAVE_FIPS_MODULE
# ifdef ENABLE_KEY_PROVIDERS
	return EVP_default_properties_is_fips_enabled(pkixssh_libctx);
# else
	return FIPS_mode();
# endif
#else
	return 0;
#endif
}

#ifdef HAVE_FIPS_MODULE
static int
ssh_enable_fips(int onoff) {
#ifdef ENABLE_KEY_PROVIDERS
	return EVP_default_properties_enable_fips(pkixssh_libctx, onoff);
#else
	return FIPS_mode_set(onoff);
#endif
}
#endif


#if defined(HAVE_FIPS_MODULE) && defined(HAVE_ERR_LOAD_FIPS_STRINGS)
# if !HAVE_DECL_ERR_LOAD_FIPS_STRINGS
/* Note function is void in OpenSSL FIPS 1.2/2.0 module i.e., OpenSSL 0.9.8+
 * or 1.0.1+/1.0.2+, and int in vendor OpenSSL 1.1.1+ FIPS patch.
 */
void ERR_load_FIPS_strings(void);
# endif
#endif

void
ssh_crypto_load_error_strings(void) {
#ifdef HAVE_OPENSSL_INIT_CRYPTO
	OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
#else
	ERR_load_crypto_strings();
#endif
#if defined(HAVE_FIPS_MODULE) && defined(HAVE_ERR_LOAD_FIPS_STRINGS)
/* load explicitly as is not loaded in some vendor libraries */
	ERR_load_FIPS_strings();
#endif
}


#if !defined(HAVE_ERR_ADD_ERROR_DATA) && defined(HAVE_ERR_ASPRINTF_ERROR_DATA)
#include <openssl/err.h>	/* for ERR_asprintf_error_data() */

void
ERR_add_error_data(int num, ...) {
	va_list args;
	va_start(args, num);
	ERR_add_error_vdata(num, args);
	va_end(args);
}

void
ERR_add_error_vdata(int num, va_list args) {
	char *arg;
	if (num != 1) return; /* PKIX-SSH call only with one argument */
	arg = va_arg(args, char *);
	ERR_asprintf_error_data("%s", arg);
}
#endif


#ifdef ENABLE_KEY_PROVIDERS
static int
ssh_provider_info(OSSL_PROVIDER *prov, void *cbdata) {
	UNUSED(cbdata);
	fprintf(stderr, "  - %s\n", OSSL_PROVIDER_get0_name(prov));
{	const char *prov_name_p = NULL;
	const char *prov_ver_p = NULL;

{	OSSL_PARAM request[] = {
		{ OSSL_PROV_PARAM_NAME, OSSL_PARAM_UTF8_PTR, &prov_name_p, 0, 0 },
		{ OSSL_PROV_PARAM_VERSION, OSSL_PARAM_UTF8_PTR, &prov_ver_p, 0, 0 },
		{ NULL, 0, NULL, 0, 0 }
	};
	OSSL_PROVIDER_get_params(prov, request);
}
	fprintf(stderr, "    %s, %s\n", prov_name_p, prov_ver_p);
}
	return 1;
}


void ssh_list_providers(void);

void
ssh_list_providers(void) {
	fprintf(stderr, "Providers:\n");
	OSSL_PROVIDER_do_all(pkixssh_libctx, ssh_provider_info, NULL);
}
#endif


int
ssh_FIPS_mode(int onoff)
{
	int mode = ssh_is_fips_enabled();

	if (onoff && mode)
		return 1;

	if (!onoff && !mode)
		return 1;

#ifdef HAVE_FIPS_MODULE
	if (!ssh_enable_fips(onoff)) {
		ssh_crypto_load_error_strings();
		do_log_crypto_errors(SYSLOG_LEVEL_ERROR);
		fatal("ssh_enable_fips(%s) failed", (onoff ? "on" : "off"));
	}

#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10000000L)
	if(!onoff)
		RAND_set_rand_method(NULL);
#endif

	return 1;
#else
	return 0;
#endif
}


#ifdef HAVE_OSSL_INDICATOR_SET_CALLBACK
#include <openssl/indicator.h>
static int
ssh_FIPS_indicator(const char *type, const char *desc,
    const OSSL_PARAM params[])
{
	UNUSED(params);
	error("FIPS: %s %s is not approved", type, desc);
	return 1;
}
#endif


static void
ssh_OpenSSL_startup(void)
{
#ifdef HAVE_OPENSSL_INIT_CRYPTO
	OPENSSL_init_crypto(
	#ifdef USE_OPENSSL_ENGINE
	    OPENSSL_INIT_ENGINE_ALL_BUILTIN |
	#endif
	    OPENSSL_INIT_ADD_ALL_CIPHERS |
	    OPENSSL_INIT_ADD_ALL_DIGESTS |
	    OPENSSL_INIT_LOAD_CONFIG, NULL);

#else
	OpenSSL_add_all_algorithms();

#ifdef	USE_OPENSSL_ENGINE
#if 0
/* Next two calls are useless if link is with dynamic engine. Also
 * they will initialize static engines and later OPENSSL_config load
 * engines again. This double initialization could crash application,
 * usually in application shutdown code (engine cleanup) even without
 * use of engines.
 */
	/* Enable use of crypto hardware */
	ENGINE_load_builtin_engines();
	ENGINE_register_all_complete();
#endif

	/* OPENSSL_config will load buildin engines and engines
	 * specified in configuration file, i.e. method call
	 * ENGINE_load_builtin_engines. Latter is only for
	 * dynamic engines.
	 */
	OPENSSL_config(NULL);

	/* Starting from openssl 1.0.1 ENGINE_load_builtin_engines
	 * call ENGINE_register_all_complete internally.
	 * Application should request registration for compatibility
	 * with all previous versions and it is save to request again.
	 * Note that dynamic_path in openssl engine configuration
	 * may register engine as default. If engine methods are not
	 * usable as default methods use of non-engine keys may trigger
	 * error in sign/verify operation.
	 */
	ENGINE_register_all_complete();
#endif /*def USE_OPENSSL_ENGINE*/
#endif /*def HAVE_OPENSSL_INIT_CRYPTO*/
}

void
ssh_crypto_init(void) {
	if (crypto_status) return;
	crypto_status = 1;

	ssh_OpenSSL_startup();
#ifdef HAVE_FIPS_MODULE
	if (getenv("OPENSSL_FIPS")) {
		(void) ssh_FIPS_mode(1);
	}
#endif
#ifdef HAVE_OSSL_INDICATOR_SET_CALLBACK
	if (ssh_is_fips_enabled())
		OSSL_INDICATOR_set_callback(pkixssh_libctx, ssh_FIPS_indicator);
#endif
}


static void
ssh_OpenSSL_shuthdown(void) {
#ifdef HAVE_OPENSSL_CLEANUP
	OPENSSL_cleanup();
#else
	/* clean configuration before engine:
	 * - it should clean internaly initialized engines */
	CONF_modules_unload(1);
	/* engine may provide rand implementation:
	 * - lets clean rand before engines */
	RAND_cleanup();
#ifndef OPENSSL_NO_ENGINE
	ENGINE_cleanup();
#endif
	/* engine may use extra data so clean it after engines */
	CRYPTO_cleanup_all_ex_data();
	EVP_cleanup();
	OBJ_cleanup();
#ifdef HAVE_ERR_REMOVE_THREAD_STATE
	ERR_remove_thread_state(NULL);
#else
	ERR_remove_state(0);
#endif
	ERR_free_strings();
#endif /*def HAVE_OPENSSL_INIT_CRYPTO*/
}

void
ssh_crypto_fini(void) {
	if (!crypto_status) return;
	crypto_status = 0;

	ssh_OpenSSL_shuthdown();
#ifdef HAVE_EVP_KEYMGMT_FETCH
	free(rsa_prov_name);
	rsa_prov_name = NULL;
#endif
}
#else /*ndef WITH_OPENSSL*/

int
ssh_is_fips_enabled(void) {
	return 0;
}

int
ssh_FIPS_mode(int onoff) {
	UNUSED(onoff);
	return 0;
}

void
ssh_crypto_init(void) {}

void
ssh_crypto_fini(void) {}

void
ssh_crypto_load_error_strings(void) {}
#endif /*ndef WITH_OPENSSL */
