/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Jakub Zelenka <bukka@php.net>                               |
   +----------------------------------------------------------------------+
 */

#ifndef PHP_OPENSSL_BACKEND_H
#define PHP_OPENSSL_BACKEND_H

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "php.h"
#include "php_openssl.h"

#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/crypto.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/conf.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/pkcs12.h>
#include <openssl/cms.h>

#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 4
typedef const X509_EXTENSION PHP_OPENSSL_X509_EXTENSION;
#else
typedef X509_EXTENSION PHP_OPENSSL_X509_EXTENSION;
#endif

/* number conversion flags checks */
#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION(_cond, _name, _arg_num) \
	do { \
		if (_cond) { \
			zend_argument_value_error((_arg_num), #_name" is too long"); \
			RETURN_THROWS(); \
		} \
	} while(0)
#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NULL_RETURN(_cond, _name) \
	do { \
		if (_cond) { \
			zend_value_error(#_name" is too long"); \
			return NULL; \
		} \
	} while(0)
/* check if size_t can be safely casted to int */
#define PHP_OPENSSL_CHECK_SIZE_T_TO_INT(_var, _name, _arg_num) \
	PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_INT_OVFL(_var), _name, _arg_num)
#define PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(_var, _name) \
	PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NULL_RETURN(ZEND_SIZE_T_INT_OVFL(_var), _name)
/* check if size_t can be safely casted to unsigned int */
#define PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(_var, _name, _arg_num) \
	PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_UINT_OVFL(_var), _name, _arg_num)
/* check if long can be safely casted to int */
#define PHP_OPENSSL_CHECK_LONG_TO_INT(_var, _name, _arg_num) \
	PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_LONG_EXCEEDS_INT(_var), _name, _arg_num)
#define PHP_OPENSSL_CHECK_LONG_TO_INT_NULL_RETURN(_var, _name) \
	PHP_OPENSSL_CHECK_NUMBER_CONVERSION_NULL_RETURN(ZEND_LONG_EXCEEDS_INT(_var), _name)

/* FIXME: Use the openssl constants instead of
 * enum. It is now impossible to match real values
 * against php constants. Also sorry to break the
 * enum principles here, BC...
 */
enum php_openssl_key_type {
	OPENSSL_KEYTYPE_RSA,
	OPENSSL_KEYTYPE_DSA,
	OPENSSL_KEYTYPE_DH,
	OPENSSL_KEYTYPE_EC,
	OPENSSL_KEYTYPE_X25519,
	OPENSSL_KEYTYPE_ED25519,
	OPENSSL_KEYTYPE_X448,
	OPENSSL_KEYTYPE_ED448,

	OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA,
};

/* Cipher constants, do not forget to update php_openssl_cipher_names in
 * openssl_backend_v3.c  if new constant added. */
enum php_openssl_cipher_type {
	PHP_OPENSSL_CIPHER_RC2_40,
	PHP_OPENSSL_CIPHER_RC2_128,
	PHP_OPENSSL_CIPHER_RC2_64,
	PHP_OPENSSL_CIPHER_DES,
	PHP_OPENSSL_CIPHER_3DES,
	PHP_OPENSSL_CIPHER_AES_128_CBC,
	PHP_OPENSSL_CIPHER_AES_192_CBC,
	PHP_OPENSSL_CIPHER_AES_256_CBC,

	PHP_OPENSSL_CIPHER_DEFAULT = PHP_OPENSSL_CIPHER_AES_128_CBC
};

/* Add some encoding rules.  This is normally handled through filters
 * in the OpenSSL code, but we will do that part as if we were one
 * of the OpenSSL binaries along the lines of -outform {DER|CMS|PEM}
 */
enum php_openssl_encoding {
	ENCODING_DER,
	ENCODING_SMIME,
	ENCODING_PEM,
};

#define MIN_KEY_LENGTH		384

/* Constants used in ext/phar/util.c, keep in sync and do not forget to update
 * php_openssl_digest_names in openssl_backend_v3.c if new constant added. */
#define OPENSSL_ALGO_SHA1 	1
#define OPENSSL_ALGO_MD5	2
#ifndef OPENSSL_NO_MD4
#define OPENSSL_ALGO_MD4	3
#endif
#ifndef OPENSSL_NO_MD2
#define OPENSSL_ALGO_MD2	4
#endif
/* Number 5 was used for OPENSSL_ALGO_DSS1 which is no longer available */
#define OPENSSL_ALGO_SHA224 6
#define OPENSSL_ALGO_SHA256 7
#define OPENSSL_ALGO_SHA384 8
#define OPENSSL_ALGO_SHA512 9
#ifndef OPENSSL_NO_RMD160
#define OPENSSL_ALGO_RMD160 10
#endif

#define DEBUG_SMIME	0

#if !defined(OPENSSL_NO_EC) && defined(EVP_PKEY_EC)
#define HAVE_EVP_PKEY_EC 1

/* the OPENSSL_EC_EXPLICIT_CURVE value was added
 * in OpenSSL 1.1.0; previous versions should
 * use 0 instead.
 */
#ifndef OPENSSL_EC_EXPLICIT_CURVE
#define OPENSSL_EC_EXPLICIT_CURVE 0x000
#endif
#endif

struct php_x509_request {
	CONF *global_config;	/* Global SSL config */
	CONF *req_config;		/* SSL config for this request */
	const EVP_MD * md_alg;
	const EVP_MD * digest;
	char	* section_name,
			* config_filename,
			* digest_name,
			* extensions_section,
			* request_extensions_section;
	int priv_key_bits;
	int priv_key_type;

	int priv_key_encrypt;

#ifdef HAVE_EVP_PKEY_EC
	int curve_name;
#endif

	EVP_PKEY * priv_key;

	const EVP_CIPHER * priv_key_encrypt_cipher;
};

void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME * name, int shortname);
void php_openssl_add_assoc_asn1_string(zval * val, char * key, const ASN1_STRING * str);
time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr);
int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, CONF *config);
char *php_openssl_conf_get_string(CONF *conf, const char *group, const char *name);
long php_openssl_conf_get_number(CONF *conf, const char *group, const char *name);
int php_openssl_add_oid_section(struct php_x509_request * req);
int php_openssl_spki_cleanup(const char *src, char *dest);

X509 *php_openssl_x509_from_param(
		zend_object *cert_obj, zend_string *cert_str, uint32_t arg_num);
X509 *php_openssl_x509_from_zval(
		zval *val, bool *free_cert, uint32_t arg_num, bool is_from_array, const char *option_name);
X509_REQ *php_openssl_csr_from_param(
		zend_object *csr_obj, zend_string *csr_str, uint32_t arg_num);
EVP_PKEY *php_openssl_pkey_from_zval(
		zval *val, int public_key, char *passphrase, size_t passphrase_len, uint32_t arg_num);

X509_STORE * php_openssl_setup_verify(zval * calist, uint32_t arg_num);
STACK_OF(X509) * php_openssl_load_all_certs_from_file(
		char *cert_file, size_t cert_file_len, uint32_t arg_num);
EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req);
zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t requested_key_size);

X509 *php_openssl_pem_read_asn1_bio_x509(BIO *in);
X509 *php_openssl_pem_read_bio_x509(BIO *in);
X509_REQ *php_openssl_pem_read_bio_x509_req(BIO *in);
STACK_OF(X509_INFO) *php_openssl_pem_read_bio_x509_info(BIO *in);
EVP_PKEY *php_openssl_pem_read_bio_public_key(BIO *in);
EVP_PKEY *php_openssl_pem_read_bio_private_key(BIO *in, pem_password_cb *cb, void *u);
PKCS7 *php_openssl_pem_read_bio_pkcs7(BIO *in);
CMS_ContentInfo *php_openssl_pem_read_bio_cms(BIO *in);
CMS_ContentInfo *php_openssl_d2i_bio_cms(BIO *in);
CMS_ContentInfo *php_openssl_smime_read_cms(BIO *bio, BIO **bcont);

#define PHP_SSL_REQ_INIT(req)		memset(req, 0, sizeof(*req))
#define PHP_SSL_REQ_DISPOSE(req)	php_openssl_dispose_config(req)
#define PHP_SSL_REQ_PARSE(req, zval)	php_openssl_parse_config(req, zval)

#define PHP_SSL_CONFIG_SYNTAX_CHECK(var) if (req->var && php_openssl_config_check_syntax(#var, \
			req->config_filename, req->var, req->req_config) == FAILURE) return FAILURE

#define SET_OPTIONAL_STRING_ARG(key, varname, defval)	\
	do { \
		if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_STRING) { \
			varname = Z_STRVAL_P(item); \
		} else { \
			varname = defval; \
			if (varname == NULL) { \
				php_openssl_store_errors(); \
			} \
		} \
	} while(0)

#define SET_OPTIONAL_LONG_ARG(key, varname, defval)	\
	if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_LONG) \
		varname = (int)Z_LVAL_P(item); \
	else \
		varname = defval

const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo);

int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args);
void php_openssl_dispose_config(struct php_x509_request * req);

zend_result php_openssl_load_rand_file(const char * file, int *egdsocket, int *seeded);
zend_result php_openssl_write_rand_file(const char * file, int egdsocket, int seeded);

const EVP_MD *php_openssl_get_evp_md_by_name(const char *name);
const EVP_MD *php_openssl_get_evp_md_from_algo(zend_long algo);
void php_openssl_release_evp_md(const EVP_MD *md);
const EVP_CIPHER * php_openssl_get_evp_cipher_by_name(const char *name);
const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo);
void php_openssl_release_evp_cipher(const EVP_CIPHER *cipher);

void php_openssl_backend_init(void);
void php_openssl_backend_init_common(void);
void php_openssl_backend_gshutdown(void);
void php_openssl_backend_shutdown(void);

void php_openssl_backend_init_libctx(struct php_openssl_libctx *ctx);
void php_openssl_backend_destroy_libctx(struct php_openssl_libctx *ctx);

const char *php_openssl_get_conf_filename(void);

void php_openssl_set_cert_locations(zval *return_value);

X509 *php_openssl_x509_from_str(
		zend_string *cert_str, uint32_t arg_num, bool is_from_array, const char *option_name);

X509 *php_openssl_x509_from_param(
		zend_object *cert_obj, zend_string *cert_str, uint32_t arg_num);

X509 *php_openssl_x509_from_zval(
		zval *val, bool *free_cert, uint32_t arg_num, bool is_from_array, const char *option_name);

zend_string* php_openssl_x509_fingerprint(
		X509 *peer, const char *method, bool raw, struct _php_stream *stream);

int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extension);

STACK_OF(X509) *php_openssl_load_all_certs_from_file(
		char *cert_file, size_t cert_file_len, uint32_t arg_num);

int php_openssl_check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose);

X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num);

void php_openssl_sk_X509_free(STACK_OF(X509) * sk);
STACK_OF(X509) *php_openssl_array_to_X509_sk(zval * zcerts, uint32_t arg_num, const char *option_name);

zend_result php_openssl_csr_add_subj_entry(zval *item, X509_NAME *subj, int nid);
zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ * csr, zval * dn, zval * attribs);
X509_REQ *php_openssl_csr_from_str(zend_string *csr_str, uint32_t arg_num);
X509_REQ *php_openssl_csr_from_param(
		zend_object *csr_obj, zend_string *csr_str, uint32_t arg_num);

#if !defined (LIBRESSL_VERSION_NUMBER)
#define PHP_OPENSSL_ASN1_INTEGER_set ASN1_INTEGER_set_int64
#else
#define PHP_OPENSSL_ASN1_INTEGER_set ASN1_INTEGER_set
#endif

EVP_PKEY *php_openssl_extract_public_key(EVP_PKEY *priv_key);

struct php_openssl_pem_password {
	char *key;
	int len;
};

EVP_PKEY *php_openssl_pkey_from_zval(
		zval *val, int public_key, char *passphrase, size_t passphrase_len, uint32_t arg_num);
EVP_PKEY *php_openssl_generate_private_key(struct php_x509_request * req);
void php_openssl_add_bn_to_array(zval *ary, const BIGNUM *bn, const char *name);

void php_openssl_add_bn_to_array(zval *ary, const BIGNUM *bn, const char *name);

#define OPENSSL_PKEY_GET_BN(_type, _name) php_openssl_add_bn_to_array(&_type, _name, #_name)

#define OPENSSL_PKEY_SET_BN(_data, _name) do { \
		zval *bn; \
		if ((bn = zend_hash_str_find(Z_ARRVAL_P(_data), #_name, sizeof(#_name)-1)) != NULL && \
				Z_TYPE_P(bn) == IS_STRING) { \
			_name = BN_bin2bn( \
				(unsigned char*)Z_STRVAL_P(bn), \
				(int)Z_STRLEN_P(bn), NULL); \
		} else { \
			_name = NULL; \
		} \
	} while (0);

EVP_PKEY_CTX *php_openssl_pkey_new_from_name(const char *name, int id);
EVP_PKEY_CTX *php_openssl_pkey_new_from_pkey(EVP_PKEY *pkey);

EVP_PKEY *php_openssl_pkey_init_rsa(zval *data);
EVP_PKEY *php_openssl_pkey_init_dsa(zval *data, bool *is_private);
BIGNUM *php_openssl_dh_pub_from_priv(BIGNUM *priv_key, BIGNUM *g, BIGNUM *p);
EVP_PKEY *php_openssl_pkey_init_dh(zval *data, bool *is_private);
EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private);
#if PHP_OPENSSL_API_VERSION >= 0x30000
void php_openssl_pkey_object_curve_25519_448(zval *return_value, const char *name, zval *data);
#endif
zend_long php_openssl_pkey_get_details(zval *return_value, EVP_PKEY *pkey);

zend_string *php_openssl_dh_compute_key(EVP_PKEY *pkey, char *pub_str, size_t pub_len);

BIO *php_openssl_bio_new_file(
		const char *filename, size_t filename_len, uint32_t arg_num, const char *mode);

void php_openssl_add_method_or_alias(const OBJ_NAME *name, void *arg);
void php_openssl_add_method(const OBJ_NAME *name, void *arg);

void php_openssl_get_md_methods(zval *return_value, bool aliases);
void php_openssl_get_cipher_methods(zval *return_value, bool aliases);

/* Cipher mode info */
struct php_openssl_cipher_mode {
	bool is_aead;
	bool is_single_run_aead;
	bool set_tag_length_always;
	bool set_tag_length_when_encrypting;
	bool aad_supports_vector;
	int aead_get_tag_flag;
	int aead_set_tag_flag;
	int aead_ivlen_flag;
};

static inline void php_openssl_set_aead_flags(struct php_openssl_cipher_mode *mode) {
	mode->is_aead = true;
	mode->aead_get_tag_flag = EVP_CTRL_AEAD_GET_TAG;
	mode->aead_set_tag_flag = EVP_CTRL_AEAD_SET_TAG;
	mode->aead_ivlen_flag = EVP_CTRL_AEAD_SET_IVLEN;
}

void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type);
zend_result php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv_required_len,
		bool *free_iv, EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode);

zend_result php_openssl_cipher_init(const EVP_CIPHER *cipher_type,
		EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode,
		const char **ppassword, size_t *ppassword_len, bool *free_password,
		const char **piv, size_t *piv_len, bool *free_iv,
		const char *tag, int tag_len, zend_long options, int enc);

zend_result php_openssl_cipher_update(const EVP_CIPHER *cipher_type,
		EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode,
		zend_string **poutbuf, int *poutlen, const char *data, size_t data_len,
		const char *aad, size_t aad_len, int enc);

const EVP_CIPHER *php_openssl_get_evp_cipher_by_name(const char *method);

CONF *php_openssl_nconf_new(void);

#endif
